Compare commits

..

202 Commits

Author SHA1 Message Date
k88936 d4796a90ab 布局重构:
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / Frontend tests (pull_request) Successful in 2m49s
Project CI / Native shell tests (pull_request) Failing after 15m15s
- 预览改为全区域背景层。
  - 左右面板、顶部流程条改为覆盖层,可遮挡预览。
  - 左右面板开合状态仅保存在当前 React 会话。
  - 增加左右面板“收起/展开”入口。
  - 编辑器关闭入口改为单独的 ×,继续通过 onBack 注入关闭逻辑。
  - 保留全局顶部栏与现有快捷键、业务状态、保存流程。
  - 底层页面仍由现有父级路由控制,不新增 URL 状态。
2026-09-12 19:13:02 +08:00
k88936 bd127846f7 允许分离流程在忙碌态保存结果
保留 isSeparating 属于 isAiRunning 的全局语义

为内部分离保存增加受控 allowDuringSeparation 选项
2026-09-12 13:36:30 +08:00
k88936 b1751784ba 修正切分状态写入日志格式
移除状态写入完成日志中多余的格式参数
2026-09-12 13:26:36 +08:00
k88936 379611d552 避免渲染阶段写入画布尺寸引用
在 ResizeObserver 尺寸更新回调中同步 canvasSizeRef
2026-09-12 13:20:30 +08:00
k88936 ef04a19d5a 抽取组件确认弹窗公共结构
统一替换与移除操作的确认弹窗布局

保留各操作独立文案与按钮样式
2026-09-12 13:19:59 +08:00
k88936 206b4830a1 复用原图编辑错误摘要
解析失败分支只计算一次截断响应并同时用于审计与错误返回
2026-09-12 13:19:10 +08:00
k88936 4b8e268816 统一重启切分失败通知
restartSeparation 失败时复用工作流完成上报并清理完成提示
2026-09-12 13:18:30 +08:00
k88936 7e7d6f1563 避免忙碌时继续切分隐藏恢复提示
在清理恢复对话框前检查分离与工作流忙碌状态
2026-09-12 13:18:13 +08:00
k88936 a4b1d81123 避免忙碌时重启切分丢失恢复状态
restartSeparation 在清理 sidecar 前复用并发工作流守卫
2026-09-12 13:17:52 +08:00
k88936 983b15c3a7 允许恢复流程识别已完成的素材回填
同一 Sprite 已绑定的节点直接跳过

仅对缺失 Image 或绑定其他素材报告错误
2026-09-12 13:17:38 +08:00
k88936 cdb6fcfe97 切换组件时重置检查面板展开状态
按组件身份变化同步展开状态,避免沿用上一个节点的折叠状态
2026-09-12 13:17:01 +08:00
k88936 d8a4e35232 减少切分状态写入的重复克隆
让状态写入接口借用状态并在阻塞任务中写入序列化字节

保留 schema 与大小校验,避免每批次复制完整状态树
2026-09-12 13:16:25 +08:00
k88936 0726106818 为图片分离请求设置超时
将原图编辑 HTTP 请求限制为 120 秒,避免工作流永久阻塞
2026-09-12 13:14:22 +08:00
k88936 8beade5b4d 为业务校验重试增加退避
校验失败重试复用请求错误的递增等待并记录日志
2026-09-12 13:13:56 +08:00
k88936 46984a4945 净化切分提示中的节点标识
复用提示文本清洗逻辑,阻断控制字符与反引号注入
2026-09-12 13:13:25 +08:00
k88936 a8a30ecbc8 修正切分恢复命令映射
finalize_separation 调用 finalize helper

discard_separation_recovery 调用 discard helper
2026-09-12 13:13:06 +08:00
k88936 48e669d167 合并主线并解决界面编辑器冲突
Project CI / Repository checks (pull_request) Successful in 2m44s
Project CI / Frontend tests (pull_request) Successful in 3m25s
Project CI / Backend tests (pull_request) Failing after 4m23s
Project CI / Native shell tests (pull_request) Successful in 17m48s
合并主线的 AGC Runtime、UI 工作流与开发脚本变更

保留现行 Prompt Runtime 结构并整合设计基础提示词契约断言

适配 CanvasWorld 逆缩放渲染并修正预览缩放回归测试

同步项目文档、共享契约与已退役策划资源清理
2026-09-12 11:38:50 +08:00
k88936 23fdc005fe 整理 Raw 编辑方案文档格式
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / Frontend tests (pull_request) Successful in 3m10s
Project CI / Native shell tests (pull_request) Failing after 14m49s
分隔字节边界说明与代码拆分列表
2026-09-12 11:22:50 +08:00
k88936 b8a4523a4f 补充 Raw 编辑字节边界说明
记录 raw_edit 使用 Bytes streaming multipart 的零拷贝约束

明确既有图片链路继续保持 Vec 字节契约
2026-09-12 11:20:53 +08:00
k88936 e50d678411 Merge pull request '修复[dev]下agc启动时长时间白屏' (#333) from boot-time-diagnosis into master
Project CI / Repository checks (push) Successful in 2m47s
Project CI / Frontend tests (push) Successful in 3m19s
Project CI / Backend tests (push) Successful in 7m46s
Project CI / Native shell tests (push) Successful in 18m17s
Project CI / Repository checks (pull_request) Failing after 10s
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
Reviewed-on: #333
2026-09-12 11:19:25 +08:00
k88936 9c04561419 明确 Raw 编辑零拷贝上传约束
补充 Bytes 所有权与 streaming multipart 注释

增加共享字节缓冲不转换的回归测试
2026-09-12 11:18:14 +08:00
k88936 551fb6609a 为 Raw 编辑建立独立字节类型
恢复旧图片链路的 Vec 字节契约

让新增 raw_edit 输入和结果类型独立于既有 ReferenceImage
2026-09-12 11:15:15 +08:00
k88936 d05440dcca 为 Raw 编辑建立独立字节类型
恢复旧图片链路的 Vec 字节契约

让新增 raw_edit 输入和结果类型独立于既有 ReferenceImage
2026-09-12 11:13:27 +08:00
k88936 7edf4a8f8b 恢复旧图片链路字节类型
Project CI / Repository checks (pull_request) Failing after 13s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / Frontend tests (pull_request) Successful in 2m43s
Project CI / Native shell tests (pull_request) Failing after 14m6s
撤回非 raw_edit 的全局 Bytes 迁移

保留既有 VectorEngine 与编辑器路径的 Vec 字节契约
2026-09-12 11:06:05 +08:00
k88936 727794bc95 Merge branch 'master' into boot-time-diagnosis
Project CI / Repository checks (pull_request) Successful in 2m43s
Project CI / Frontend tests (pull_request) Successful in 3m12s
Project CI / Backend tests (pull_request) Successful in 7m13s
Project CI / Native shell tests (pull_request) Successful in 20m20s
2026-09-12 10:58:33 +08:00
k88936 cc9b95ddef 优化调试配置:提升SHA-256计算性能,避免调试构建阻塞
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / Backend tests (pull_request) Failing after 14s
Project CI / Frontend tests (pull_request) Successful in 3m5s
Project CI / Native shell tests (pull_request) Successful in 20m20s
2026-09-12 10:54:51 +08:00
k88936 afc9ecbd51 补充 WebP 源图转码测试
验证 WebP 输入可转换为 PNG 上传格式
2026-09-12 10:42:15 +08:00
k88936 fc2f4b71ce 支持分离流程转换非 PNG 源图
在上传图片编辑请求前将 JPEG 和 WebP 转码为 PNG

新增 JPEG 转 PNG 单元测试并将转码放入阻塞任务
2026-09-12 10:38:00 +08:00
k88936 545f64cf9e 补齐原图编辑失败追踪事件
provider 失败时记录 external_generation_run 失败状态

保留现有外部 API 失败审计与错误映射
2026-09-12 10:37:04 +08:00
k88936 2a50614c11 将分离恢复阻塞 I/O 移出异步线程
新增 spawn_blocking 状态读取封装

sidecar 目录创建改用阻塞任务执行
2026-09-12 10:23:32 +08:00
k88936 915006c98f 适配绑定区域类型错误调用方
在区域归一化边界转换 typed error 为展示文案
2026-09-12 10:23:21 +08:00
k88936 69ce232a74 为绑定区域校验引入类型错误
用 ZeroDimension 和 OutOfBounds 区分边界校验失败

补充 typed error 单元测试并保留展示层中文文案
2026-09-12 10:20:17 +08:00
k88936 319aa69d6e 统一分离树根节点坐标空间
子节点收集复用根节点实际解析矩形
2026-09-12 02:24:01 +08:00
k88936 bec468bdc9 统一分离终止节点判定
批次选择与提取提示复用同一终止节点集合
2026-09-12 02:23:02 +08:00
k88936 eada20c199 恢复自动切分前重新校验状态
继续分离前确认 sidecar 存在并检查前置条件

恢复入口异常统一写入工作流失败通知
2026-09-12 02:20:52 +08:00
k88936 e8cf773aac 统一自动切分失败完成提示
分离入口异常时写入工作流完成失败通知
2026-09-12 02:19:48 +08:00
k88936 12fbb61fad 限制分离恢复状态写入大小
写盘前校验与读取一致的八兆字节上限

超限时记录原因并拒绝不可恢复检查点
2026-09-12 02:17:55 +08:00
k88936 478e08fb92 将 PNG 依赖限定为测试环境
从生产依赖移除仅测试使用的 png crate

在 dev-dependencies 中声明 PNG 测试依赖
2026-09-12 02:13:52 +08:00
k88936 0e67fbf80a 停止对最终工作流步骤重复预检
资产分离作为最后步骤不再校验不存在的下一步
2026-09-12 02:12:15 +08:00
k88936 ed2cfbad36 避免预览容器变化重置缩放
自动适配仅在图片或逻辑尺寸变化时执行

通过尺寸引用保留初始视口兜底值
2026-09-12 02:11:22 +08:00
k88936 560422286b 稳定组件面板展开与移除交互
仅在组件存在性变化时自动展开面板

为移除组件增加确认并保留失败弹窗
2026-09-12 02:08:56 +08:00
k88936 e04075fd95 为分离模型请求增加重试退避
瞬时请求失败时按递增毫秒延迟后重试

将重试循环末尾改为不可达断言
2026-09-12 02:07:02 +08:00
k88936 d7776f2a17 修正文生绑定提示语法
改进分离要求和返工建议英文表述
2026-09-12 02:05:18 +08:00
k88936 4d9e69841e 加固分离提示词生成
绑定提示改用切片参数避免多余复制

清理提取提示中的描述与返工备注文本
2026-09-12 02:04:32 +08:00
k88936 49e88bbd9f 统一分离提示备注长度限制
复用模型层的重做备注字符上限

导出提示文本清理函数供投影复用
2026-09-11 21:55:15 +08:00
k88936 86c268eca3 分离恢复使用独立丢弃操作
新增独立的恢复状态丢弃入口

完成与 finalize 的共享状态删除逻辑
2026-09-11 21:54:17 +08:00
k88936 c86221e3da 添加 TODO 注释以提示恢复字符限制错误信息
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Successful in 3m3s
Project CI / Native shell tests (pull_request) Failing after 14m57s
2026-09-11 20:12:16 +08:00
k88936 e7af7cf4e4 校验处理图区域并明确 multipart 字节上传
将处理图真实尺寸传入视觉绑定校验,提前拒绝零尺寸和越界区域。

保持非法区域直接失败,不转换为 NeedRework。

将 image 和 mask multipart 部分改为 Part::bytes。
2026-09-11 20:11:11 +08:00
k88936 e7cd008d7c 调整文本组件的识别规则
简化多行文本节点逻辑,明确文字与图片组件的绑定要求。
2026-09-11 20:06:09 +08:00
k88936 c743ca146e 复用图像下载的共享字节缓冲
将 DownloadedImage.bytes 改为 bytes::Bytes。

让远程下载和图标切片下游避免不必要的完整复制。

同步更新图像处理实现与测试 fixture。
2026-09-11 19:53:14 +08:00
k88936 8c8b6cc75a 将自动切分状态写盘移出 Tokio worker
用 spawn_blocking 承载 JSON 序列化和文件写入,并在工作流中等待结果。
2026-09-11 19:44:01 +08:00
k88936 4cf3642b53 说明视觉绑定解析错误的有限重试策略
记录严格工具 schema 和响应损坏重试边界。
2026-09-11 19:41:48 +08:00
k88936 f933db169e 说明结构化重试的有限上下文边界
记录现役调用方的小重试次数和保留完整反馈的设计依据。
2026-09-11 19:41:21 +08:00
k88936 a5e936dd27 清理已退役的前端绑定模块
删除无现役导入的 binding.ts 及其 BindingDTO、BindingChange 类型。
2026-09-11 19:39:59 +08:00
k88936 53008959c5 修正自动切分结果的类型收窄
在异步闭包赋值后明确已完成结果的非空类型。
2026-09-11 19:09:22 +08:00
k88936 57dbb3de52 替换已有组件前增加确认
使用独立确认弹窗避免一次点击覆盖已配置组件。
2026-09-11 19:07:12 +08:00
k88936 e189aff485 更新设计基础提示词契约测试
将测试断言同步到当前按需图片生成与动态产物合同。
2026-09-11 19:05:57 +08:00
k88936 248c094da9 清理合并命令测试中的无用导入
移除组件类型未再使用的测试模块导入。
2026-09-11 19:05:03 +08:00
k88936 e66eb2fb55 切换预览图片时自动适配画布
在活动图片变化后重新按完整画布边界计算视口。
2026-09-11 19:04:33 +08:00
k88936 d455dc18a9 按实际保存快照更新脏状态签名
保存成功后直接记录持久化快照签名,避免闭包中的旧编辑器状态阻止清脏。
2026-09-11 19:03:51 +08:00
k88936 04a0d21351 避免原始 PNG 的重复解析
使用解码后的颜色类型校验位深,将读取限制统一留在 image 解码器。
2026-09-11 19:03:03 +08:00
k88936 a9358bf3ce 防止字体前置校验访问空值
对外部状态中的空字体引用返回正常校验结果而不是抛出异常。
2026-09-11 19:00:49 +08:00
k88936 c3de2ce958 为自动切分命令增加项目写权限校验
在启动分离工作流前复用 asset.register 权限门禁。
2026-09-11 19:00:19 +08:00
k88936 287d2f94da 限制自动切分恢复状态读取大小
在解析 sidecar state 前检查文件大小,避免异常分配。
2026-09-11 18:59:51 +08:00
k88936 1343f487d0 为自动切分工作流增加忙碌保护
统一直接调用入口的并发防护,避免状态锁冲突。
2026-09-11 18:59:10 +08:00
k88936 73fb89a8f4 阻止本地素材登记结果错位回填
资源导入返回数量与请求路径不一致时立即中止位置映射。
2026-09-11 18:58:35 +08:00
k88936 bf2ffd2bf9 拒绝保存无可见像素的切分区域
透明裁切区域直接返回可恢复错误,避免登记空素材。
2026-09-11 18:58:13 +08:00
k88936 3f55f043a1 拒绝切分产物路径中的非法节点标识
在写入 cut 图片前限制节点 ID 字符并确认路径仍位于 sidecar 内。
2026-09-11 18:57:19 +08:00
k88936 26677b193c 复用界面树组件计数值
在同一行中复用组件数量,避免重复计算。
2026-09-11 18:56:13 +08:00
k88936 b8691add3a 移除区域归一化日志路径中的恐慌
直接记录归一化结果,避免诊断日志调用 expect。
2026-09-11 18:55:28 +08:00
k88936 bfe010a192 让视觉绑定调试预览写入失败可降级
调试 PNG 写入失败时记录警告并继续绑定流程。
2026-09-11 18:55:06 +08:00
k88936 6db6de5b99 同步检查器组件面板展开状态
切换节点或组件时重置展开状态,避免复用旧节点的折叠状态。
2026-09-11 18:54:39 +08:00
k88936 87a7294a04 细化组件绑定载荷形状校验
在反序列化前拒绝空值和无效的节点组件外部枚举。
2026-09-11 18:54:21 +08:00
k88936 3774d7c1e1 移除修复重试流程中的隐式恐慌
在循环意外结束时返回可恢复错误而不是触发 unreachable。
2026-09-11 18:53:34 +08:00
k88936 8d15125f12 限制切分提示词中的节点备注
对用户备注清理控制字符和围栏符号并限制长度。
2026-09-11 18:52:59 +08:00
k88936 cf68b8212d 让提取提示词序列化错误可恢复
将提取提示词生成改为返回带上下文的错误。
2026-09-11 18:52:10 +08:00
k88936 8d7ccc0d75 简化工作流操作卡状态映射
使用步骤映射替代嵌套三元表达式。
2026-09-11 18:51:30 +08:00
k88936 2af3d9a965 修正结构识别步骤的前置校验
将结构识别活动步骤改为校验素材切分前置条件。
2026-09-11 18:50:59 +08:00
kdletters fafe6b63cd 修复AGC模型请求与对话登录态自动续期 (#329)
Project CI / Repository checks (push) Successful in 2m40s
Project CI / Frontend tests (push) Successful in 3m23s
Project CI / Backend tests (push) Successful in 5m46s
Project CI / Native shell tests (push) Successful in 18m30s
## 变更说明

- AGC 客户端配套后端 API 在 401 时自动刷新登录态并重试一次,覆盖模型目录请求。
- DirectProject 对话鉴权失效时刷新平台会话、同步 Rust/Runner 凭据并重试同一 clientTurnId。
- 403 权限拒绝不触发续期;账号切换后不重发旧请求。
- 补充并发续期、失败保留原错误、权限边界和 Direct 对话重试测试。

## 验证

- npm run test -- apps/ai-game-creator-shell/tests/appSurface.test.ts apps/ai-game-creator-shell/tests/clientApi.test.ts apps/ai-game-creator-shell/tests/clientHttp.test.ts apps/ai-game-creator-shell/tests/conversationModelSelect.test.tsx
- npm run agc:typecheck
- npm run check:encoding
- git diff --check

Reviewed-on: #329
Co-authored-by: kdletters <kdletters@qq.com>
Co-committed-by: kdletters <kdletters@qq.com>
2026-09-11 18:09:48 +08:00
k88936 4749339bad 补齐图片字节类型导出与转换
Project CI / Repository checks (pull_request) Failing after 31s
Project CI / Backend tests (pull_request) Failing after 13s
Project CI / Frontend tests (pull_request) Successful in 3m56s
Project CI / Native shell tests (pull_request) Failing after 3m33s
导出共享 GPT 图片尺寸阈值常量。

修复编辑器参考图 Bytes 与 Vec 边界转换。
2026-09-11 18:02:04 +08:00
k88936 3e85c71060 整理图片代理代码格式
按 Rust 格式化规范整理 multipart 与响应解析代码。
2026-09-11 17:55:50 +08:00
k88936 da19bde99a 同步八位 PNG 与 b64 契约
记录八位 PNG 校验规则与稳定错误文案。

说明 GPT-Image-2 原生 b64_json,不发送 response_format。
2026-09-11 17:52:26 +08:00
k88936 4a32ee37c4 修复 Bytes 迁移后的图片类型边界
统一使用 Bytes 的只读引用并在旧 Vec 接口处显式转换。

修复编辑器参考图对齐与 Raw PNG 解码编译错误。
2026-09-11 17:52:00 +08:00
k88936 31fd1da495 修正 GPT 图片响应契约说明
仅保留 GPT-Image-2 原生 b64_json 行为的行内注释。

不发送 response_format 参数,也不引入 URL 兼容下载。
2026-09-11 17:48:55 +08:00
k88936 4c88e5e924 限制 Raw 图片仅接收八位 PNG
使用 png 解码器读取位深并在 image 解码前拒绝非八位输入。

统一返回稳定的 8-bit PNG 客户端错误并补充十六位 PNG 测试。
2026-09-11 17:48:01 +08:00
k88936 99f4961c5f 统一 GPT 图片尺寸计费阈值
提取共享的 1536 长边 tier 常量并复用到 raw 与编辑器计费。

同步 Raw 图片代理技术合同中的阈值来源。
2026-09-11 17:47:31 +08:00
k88936 0cebc39b6a 同步 Raw 图片代理契约
记录文本字段 16KiB 流式限制。

记录 GPT-Image-2 固定 b64_json 响应格式。
2026-09-11 17:37:58 +08:00
k88936 842d080a75 锁定图片缓冲依赖
将 platform-image 使用的 bytes 依赖写入 Cargo.lock。
2026-09-11 17:37:32 +08:00
k88936 8cd6e34af0 调整图片切分批次请求尺寸计算逻辑
基于批次源矩形总面积动态计算 image-edit 请求尺寸,新增面积对齐和最小/最大值限制,扩展相关模块和测试以支持调整后的工作流。
2026-09-11 17:33:26 +08:00
k88936 b357d36262 明确 GPT 图片编辑响应格式
原始图片编辑请求固定声明 response_format=b64_json。

补充 GPT-Image-2 仅返回 b64_json 的契约行内注释。
2026-09-11 17:29:11 +08:00
k88936 48c5638893 扩大原始图片文本字段上限
multipart 文本字段改为 chunk 流式读取并限制 16KB。

统一 UTF-8 校验和超限错误,避免先完整分配超长 prompt。
2026-09-11 17:28:13 +08:00
k88936 46df12c086 收敛原始图片 multipart 错误
服务端记录解析原始错误,客户端仅返回稳定分类文案。
2026-09-11 17:26:33 +08:00
k88936 4bdbab29c0 优化原始图片请求缓冲
将 ReferenceImage 与 multipart 输入改用 bytes::Bytes。

通过流式 multipart part 避免 reqwest 路径的额外整图复制。
2026-09-11 17:25:25 +08:00
k88936 9acc23b848 抽离 DFS 批次逻辑至独立模块,并优化节点面积计算与终端过滤流程
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Successful in 8m16s
Project CI / Native shell tests (pull_request) Failing after 11m7s
2026-09-11 15:34:30 +08:00
k88936 b9b4ebd38e 重命名图片编辑为提取模块并更新相关调用绘制流程
Project CI / Repository checks (pull_request) Failing after 16s
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / Native shell tests (pull_request) Failing after 4m22s
Project CI / Frontend tests (pull_request) Successful in 11m36s
2026-09-11 15:18:05 +08:00
k88936 8f680ef376 视觉绑定增加透明标记预处理
新增视觉绑定 RGBA 预处理与阈值复用

在请求前生成不透明洋红标记图并记录耗时

更新英文提示词说明标记色语义
2026-09-11 15:09:44 +08:00
k88936 7c7d55dcab 重构提取提示词生成逻辑,移除文本拼接方式,新增 YAML 序列化结构与测试 2026-09-11 14:54:00 +08:00
k88936 1303592a86 重构UI编辑器素材切分工作流模块
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Successful in 5m17s
Project CI / Native shell tests (pull_request) Failing after 6m41s
拆分提取与绑定提示词模块

拆分图片编辑、视觉绑定、裁切和批次 patch 实现

同步技术方案实现组织说明并更新提示词测试
2026-09-11 13:54:33 +08:00
k88936 1db7597141 重构UI素材切分树与批次选择
移除文本遮罩和标记图生成流程,图片编辑直接读取原始界面图

引入节点类型并保留文字移除上下文,按前序DFS与面积预算选择图片批次

补充根节点、重叠节点、文字过滤和超面积推进测试并同步TypeScript类型
2026-09-11 12:55:53 +08:00
k88936 13701d0144 澄清UI自动切分的原图输入与调试产物
明确图片编辑直接使用原始界面图,不再生成辅助输入图

明确处理图与裁切图使用随机文件名并保留用于调试
2026-09-11 11:56:33 +08:00
k88936 5c83142fec 更新UI编辑器自动切分素材工作流方案
将多轮整图分层与DFS面积批次写入权威专题

明确TextRemovalOnly、视觉绑定和atlas输出合同

删除平行专题文档并保留后续TODO

补充UI编辑器自动分离多轮分层方案

新增整图语义分层与多轮 image-edit 方案

明确 DFS 批次、TextRemovalOnly 和视觉绑定合同

记录面积预算、恢复边界与后续 TODO
2026-09-11 11:10:51 +08:00
k88936 95ed49f264 调整素材切分边界像素上限
将视觉绑定区域每条边的调整上限改为固定 32px。

同步 UI 编辑器自动切分技术方案与项目决策记录。

更新边界测试以覆盖绝对像素限制。
2026-09-11 10:28:32 +08:00
k88936 b8d8072267 完善识别命令说明,新增禁止冗余背景节点的指引
Project CI / Repository checks (pull_request) Failing after 9s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / Frontend tests (pull_request) Successful in 2m44s
Project CI / Native shell tests (pull_request) Failing after 3m5s
2026-09-10 19:54:46 +08:00
k88936 462caa33c5 修复工作流完成弹窗的多行错误展示
让完成通知中的换行字符按实际换行渲染,避免多项回填错误挤成一行。

保持通知内容和弹窗交互不变。
2026-09-10 19:06:20 +08:00
k88936 4e1064266f 移除自动切分结果的冗余类型断言
依赖空值检查后的 TypeScript 类型收窄直接使用 SeparationDTO。

不改变自动切分流程或运行时行为。
2026-09-10 19:05:48 +08:00
k88936 a50cbfb6c8 过滤图片编辑响应中的空图像数据
忽略上游返回的空 b64_json 条目,避免把空字符串转发为图像结果。

新增空条目与有效条目混合响应的回归测试。
2026-09-10 19:04:40 +08:00
k88936 1eb4bd5ec0 明确拒绝无效的处理图数据地址
处理图缺少 data URL 分隔符时直接返回格式错误。

避免先写入空文件再在后续裁切阶段暴露误导性错误。
2026-09-10 19:03:29 +08:00
k88936 5b441d389c 忽略完全位于图片外的切分标记矩形
矩形起点超出图片边界时直接返回 None,避免在边缘绘制伪影。

新增越界矩形回归测试。
2026-09-10 19:02:40 +08:00
k88936 f200cb4d3c 避免标记图编码重复复制像素缓冲
复用同一个 DynamicImage 完成文件保存和返回数据编码。

保持标记图内容和错误处理不变。
2026-09-10 19:02:09 +08:00
k88936 ae2f5fc80b 记录 UI 编辑器重试中的请求错误
在结构化 LLM 请求的中间重试失败时写入尝试次数和错误日志。

保留原有重试次数、历史消息和最终错误语义。
2026-09-10 19:01:32 +08:00
k88936 b69bd88e5e 修正 UI 编辑器校验器命名
将 run_with_repair_history 的 Validater/validater 更正为 Validator/validator。

不改变重试流程和运行时行为。
2026-09-10 19:01:18 +08:00
k88936 c5c7339c4a 重命名分离模块 problem_descriptionadvice 并同步更新相关校验逻辑与测试
Project CI / Repository checks (pull_request) Failing after 13s
Project CI / Backend tests (pull_request) Failing after 9s
Project CI / Frontend tests (pull_request) Successful in 2m28s
Project CI / Native shell tests (pull_request) Failing after 14m32s
2026-09-10 18:54:10 +08:00
k88936 4bbc77fc32 增加UI自动切分耗时日志
补充 marker、area、LLM、图片编辑及 batch 阶段耗时观测
记录成功与失败路径的性能字段
同步自动切分工作流技术方案中的日志约定
2026-09-10 18:51:53 +08:00
k88936 8c521ca891 移除 UI 预览适配与缩放输入
删除预览工具栏适配按钮、适配回调与对应快捷键
将缩放百分比改为只读显示并移除输入组件及测试
2026-09-10 18:48:56 +08:00
k88936 69a0804c03 调整切分区域边界扩展上限
将像素边界每边调整上限常量设为百分之百

同步更新受影响的区域归一化测试

技术方案仅引用上限常量,不重复写死数值
2026-09-10 18:46:39 +08:00
k88936 74f726e65d 修正自动切分素材检查条件
移除自动切分对预先导入独立素材的错误要求。
统一自动切分前置与结果检查,并补齐图片和字体引用校验。
在新建和重新开始自动切分时执行前置检查,恢复流程继续尽力完成。
2026-09-10 18:43:31 +08:00
k88936 1509235c1b 统一自动切分素材工作流命名
将 UI 编辑器第三步和内部流程 ID 统一为自动切分素材。
重命名前端切分概览模块并更新完成、恢复和导航文案。
同步 Runtime 最终编辑器路由、测试与相关技术文档。
2026-09-10 15:11:29 +08:00
k88936 1c992c69fe 完善分离与识别命令说明,调整嵌套格式与结构化指引 2026-09-10 14:05:37 +08:00
k88936 d9b153ed78 完善分离命令说明,新增禁止未请求文本的指引 2026-09-10 13:15:42 +08:00
k88936 30c469e9a1 规范分离模块格式化写法 2026-09-10 13:15:34 +08:00
k88936 51f629b2f9 UI编辑器节点改为单组件模型
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / Backend tests (pull_request) Failing after 14s
Project CI / Frontend tests (pull_request) Successful in 2m43s
Project CI / Native shell tests (pull_request) Failing after 14m29s
修改 Node、Binding DTO、识别与分离链路为 component 单字段

为识别和绑定 LLM 工具引入 PureNode/WithComponent 显式载荷

移除前端组件索引、排序和组件栈操作

同步生成 ts-rs TypeScript 类型、状态校验、测试与专题文档
2026-09-10 13:08:29 +08:00
k88936 d6c720ecdd 纠正分离模块导出提交边界
撤销误入的组件字段重命名测试改动

仅保留裁切工作流对面积归一化模块的直接依赖
2026-09-10 12:26:39 +08:00
k88936 086aa91d70 收窄分离区域模块导出
让裁切工作流直接依赖面积归一化模块

移除未使用的公共重导出,保持分离模块边界清晰
2026-09-10 12:24:18 +08:00
k88936 3733e86272 补充分离区域像素归一化约定
记录透明像素边界扫描、方向锁定与四边同步推进规则

明确 50% 与图像边界约束、透明图处理、日志字段和性能 TODO
2026-09-10 12:13:21 +08:00
k88936 5d0f3e6be6 UI编辑器分离区域像素边界归一化
新增独立 BindingArea 像素边界归一化模块与回归测试

在 cut 裁切前按透明像素收缩或扩展四条边并记录约束结果

接入分离工作流并保留原始与归一化区域日志
2026-09-10 12:13:04 +08:00
k88936 989440173f 完善绑定区域值类型约束
为 BindingArea 增加 Eq 以支持分离区域比较
2026-09-10 11:52:46 +08:00
k88936 22d9958d18 在分离标记图中遮罩普通文字
绘制文字紫色区域并保持绿色元素框在最上层

增加文字遮罩图层顺序测试
2026-09-10 10:57:50 +08:00
k88936 56f8c9a6a1 构造自动分离文字遮罩
按最近未绑定图片祖先收集 Text 布局像素矩形

补充嵌套图片与根图片遮罩构造测试
2026-09-10 10:49:45 +08:00
k88936 c84df3d88f 补充文字遮罩状态字段契约
明确 text_mask_areas 为当前 separation state 必选字段

保持开发阶段 v1 且不提供旧 sidecar 迁移回退
2026-09-10 10:49:07 +08:00
k88936 761d32cebf 拆分自动分离模型并增加文字遮罩区域
将 separation model 拆为 note、node、binding、result 模块

为 SeparationNode 增加 text_mask_areas 字段并同步 TypeScript 类型
2026-09-10 10:43:00 +08:00
k88936 b3d6543d73 补充自动分离文字遮罩方案
记录普通文字归属、遮罩字段与标记图绘制规则

明确不进入分离树、绑定结果和视觉模型契约
2026-09-10 10:32:34 +08:00
k88936 8cd7c43b9e 更新自动分离接入文档状态
将前端接入 TODO 更新为联调与失败注入测试待办。
2026-09-09 21:02:09 +08:00
k88936 7fb7d246c0 将视觉步骤切换为自动分离
前端仅调用一次 separate_ui,并完成 cut 图片登记、SpriteAsset 回填和 State 保存。

增加 sidecar 恢复选择面板,旧 bind_components 标记为未调用遗留命令。
2026-09-09 21:00:53 +08:00
k88936 5844afa14d 抽取自动分离素材状态变换
复用 SpriteAsset 校验并支持锁内构造下一份 State。

保留公开 addSpriteAssets 的锁保护与历史提交行为。
2026-09-09 20:17:11 +08:00
k88936 615d1a28cd 补齐自动分离恢复状态胶水
新增 separation 恢复探测、完成清理命令与 DTO。

持久化项目相对 cut 图片路径并保留 state sidecar 到最终提交。
2026-09-09 20:16:09 +08:00
k88936 3e1e172f9c 补充自动分离前端接入合同
明确 UI 编辑器单次调用 separation 与 sidecar 恢复弹窗

补充资源登记、State 回填、保存和 finalize 顺序

划定旧 binding 与 Runtime workflow 的本次不改范围
2026-09-09 20:03:27 +08:00
k88936 53a919e39b 优化 UI 分离工作流程的提示生成与历史处理
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / Backend tests (pull_request) Failing after 14s
Project CI / Frontend tests (pull_request) Successful in 2m50s
Project CI / Native shell tests (pull_request) Failing after 3m4s
- 调整 initial_history 构建逻辑,增强系统与用户消息的一致性。
- 简化 SeparationNode 提示生成逻辑,仅保留节点 ID 与注释。
- 明确 extracted_area 的权威性为 processed 图像,并更新生成提示的规则。
2026-09-09 19:04:56 +08:00
k88936 d4b5f9fc14 重构 UI Editor LLM repair history
将 request_with_feedback 改为 run_with_repair_history 并统一 max_retries 与 validater 命名。

让 requester 接收 append-only LlmMessage history,业务校验失败追加序列化响应和错误反馈。

迁移 visual_binding 调用方并补充 retry-only 与业务反馈 history 测试。

同步 UI Editor 结构化请求 repair history 决策文档。
2026-09-09 16:46:29 +08:00
k88936 6e4f9a6f1f 完善 UI 自动分离返工意见链路
记录视觉模型 NeedRework 意见并注入后续提取提示

同步返工次数、problematic 终态和 512 字符校验

补充 separation DTO 类型、回归测试与技术方案
2026-09-09 16:29:54 +08:00
k88936 8a0dcf8be7 增强分离标记框的可视辨识度
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Successful in 3m4s
Project CI / Native shell tests (pull_request) Failing after 4m38s
为分离标记框增加两条绿色角到角交叉线。

新增 MARKER_LINE_WIDTH 常量统一配置标记线宽,并确保绘制结果裁剪在矩形内。

补充标记框像素级回归测试并同步更新自动分离专题文档。
2026-09-09 16:24:11 +08:00
k88936 aa2a78f7e0 重命名 separated_image_area 为 extracted_area,优化命名一致性并明确区域含义 2026-09-09 15:48:32 +08:00
k88936 7aa96e8438 统一分离标记图中文注释
说明紫色重建与绿色框绘制层级
2026-09-09 15:16:39 +08:00
k88936 feb6bc0ad4 修正分离失败恢复边界
图片编辑、写入和裁切失败保留状态并返回错误
仅视觉模型返工结果进入 problematic
2026-09-09 15:07:49 +08:00
k88936 f349350d60 同步分离状态 TypeScript 类型
加入 root_extractable 并将树字段统一为 trees
2026-09-09 15:03:48 +08:00
k88936 df3a172bc9 补充 Raw GPT Image 2 multipart 合同
明确分离图片编辑请求字段与旧协议边界
2026-09-09 14:57:55 +08:00
k88936 9b4924c73e 补充分离批次重叠筛选测试
验证 DFS 顺序下贪心跳过重叠叶节点
2026-09-09 14:54:37 +08:00
k88936 d30bc462c5 格式化自动分离 Rust 模块
统一树选择、标记图和批次流程代码风格
2026-09-09 14:50:19 +08:00
k88936 26b9ad7fad 重构分离树与非重叠批次选择
保留真实根节点并记录 root_extractable
按终态反查和 DFS 贪心筛选逻辑叶
拆出父子区域标记图生成模块
2026-09-09 14:48:18 +08:00
k88936 bc5398894e 补充分离树与批次算法约定
明确真实根节点、逻辑叶和非重叠贪心选择
补充父节点紫色重建与错误恢复边界
2026-09-09 14:29:38 +08:00
k88936 11d4d3d930 适配 UI 分离图片编辑 multipart 接口
Project CI / Frontend tests (pull_request) Successful in 4m57s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Native shell tests (pull_request) Failing after 5m40s
将标记图 data URL 解码为 PNG 文件部件。

按 Raw GPT Image 2 新合同发送 prompt、尺寸和输出参数。

保留响应 data[].b64_json 解析与 separation 流程不变。
2026-09-09 13:17:36 +08:00
k88936 418d5732de 调整UI识别指令,细化粒度要求并优化组件返回规则 2026-09-09 13:12:51 +08:00
k88936 592b6b4ee7 简化识别树验证测试中的断言格式 2026-09-09 13:12:51 +08:00
k88936 99c19e3ed9 补充分离流程排障日志
记录 separation sidecar 状态读写与恢复阶段

记录树构造、批次选择、视觉绑定、裁切和 patch 结果

避免输出 base64、完整提示词和敏感凭据
2026-09-09 13:12:51 +08:00
k88936 15e30acb0f 调整UI识别指令细节,明确文字组件识别规则与粒度标准 2026-09-09 13:12:51 +08:00
k88936 56fff8325b 将自动分离图像处理移出异步执行器
在 spawn_blocking 中构建标记图并编码写入处理图

在 spawn_blocking 中完成处理图裁切与文件输出

保留 image-edit 和视觉绑定网络请求的 async 调度边界
2026-09-09 13:12:51 +08:00
k88936 884c5ec5ee 补充自动分离 sidecar 并发边界约束
记录前端单次运行与 runWithStateLocked 的并发前提

明确 sidecar 不参与正式资产写入和项目 revision

补充未来多窗口多进程场景的进程级锁 TODO

记录图像本地处理使用 spawn_blocking 的执行边界
2026-09-09 13:12:51 +08:00
k88936 207b0820dd 统一分离树文件格式
按仓库 rustfmt 规范整理 synthetic root 实现
2026-09-09 13:12:51 +08:00
k88936 19f3d62d78 隔离非法分离区域导致的批次失败
将裁切错误记录为 problematic 并继续流程
避免单个模型区域终止整页分离
2026-09-09 13:12:51 +08:00
k88936 3c1e7bd599 保留分离问题节点的真实重做次数
避免基础设施失败伪造达到上限
保持 problematic 节点诊断信息准确
2026-09-09 13:12:51 +08:00
k88936 dda6b3946a 阻止识别结果引用未授权字体素材
补齐识别阶段字体引用校验
增加伪造字体绑定回归测试
2026-09-09 13:12:51 +08:00
k88936 aff40f794d 修复分离树合成根节点 ID 冲突
避免单图根节点重复进入重做计数
补充根图片构造回归测试
2026-09-09 13:12:50 +08:00
k88936 187faa607d 保留原始分离提示并拆分模块
按模型、树、持久化、提示词和工作流拆分 separation

提取支持 more_turn 的反馈重试 harness

删除旧 separation.rs 文件
2026-09-09 13:12:50 +08:00
k88936 109cc81740 拆分UI自动分离命令模块
保留原始图片分离与绑定提示词

提取模型、树、持久化和工作流子模块

新增可配置 more_turn 反馈重试 harness
2026-09-09 13:12:50 +08:00
k88936 f796aef7e2 完善UI自动分离批次处理
复用资源ID安全路径并修正像素坐标

加入标记图、Raw编辑、视觉绑定修复与问题节点继续处理
2026-09-09 13:12:50 +08:00
k88936 4cc7f4dd64 实现UI自动分离命令
新增分离树、批次处理与sidecar状态

接入Raw GPT Image 2与视觉绑定重试

注册Tauri命令并补充识别组件草稿
2026-09-09 13:12:50 +08:00
k88936 b2d4690f94 补充UI编辑器自动分离工作流合同
新增自动分离、临时 sidecar 与视觉绑定设计合同

明确前端资产登记职责与恢复 TODO
2026-09-09 13:12:50 +08:00
k88936 0400103a19 将 Raw 图片编辑入站改为 multipart
移除 JSON/base64 入站兼容,仅接受 multipart 图片与参数字段

复用扣费前 PNG、尺寸、mask 和 prompt 校验并补充 multipart 测试

优化上游图片字节转发,避免再次克隆 image bytes

同步 Raw 图片编辑技术方案与 Axum multipart 依赖
2026-09-09 13:06:22 +08:00
k88936 c049d68b8e 收紧 Raw 成功响应字段类型
要求 data 与 b64_json 必填并移除无用 provider id

prompt 上限调整为 4 KiB 原始字节并同步文档
2026-09-09 12:03:03 +08:00
k88936 acfcf0c158 同步 platform-image 的 serde 锁定依赖
为 Raw typed 响应记录 serde workspace 依赖
2026-09-09 11:51:25 +08:00
k88936 5bbedd9a5c 限制 Raw prompt 原始字节大小
将 prompt UTF-8 原始字节限制为 16 KiB

补充超限回归测试并同步请求合同
2026-09-09 11:50:30 +08:00
k88936 40316d82c4 在扣费前校验 Raw mask 尺寸
解码时保留 image 与 mask 宽高并拒绝不一致输入

同步 Raw 图片编辑请求合同与回归测试
2026-09-09 11:45:44 +08:00
k88936 6c77de4a36 为 Raw 成功响应收紧类型透传
使用 typed JSON DTO 提取并原样转发 b64_json

不解码图片内容且忽略 output_format 回显
2026-09-09 11:44:31 +08:00
k88936 b3693765ef 透传 Raw 图片结果避免解码回显字段
仅转发上游 data.b64_json 并忽略 output_format

同步 Raw 技术方案并补充不解码回归测试
2026-09-09 11:34:41 +08:00
k88936 f7edc9d0ee 区分 Raw 图片 PNG 资源限制错误
Project CI / Frontend tests (pull_request) Successful in 4m43s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / Native shell tests (pull_request) Failing after 5m14s
将有效但超出解码限制的 PNG 映射为明确的 400 提示

补充超大合法 PNG 的回归测试
2026-09-09 11:04:44 +08:00
k88936 f60771dd00 提前检查 Raw 图片请求预算
在构造 multipart 表单前校验请求 deadline

避免预算耗尽时复制输入图片并缩短耗时统计范围
2026-09-09 10:58:48 +08:00
k88936 f7cccd8592 补充 Raw 图片客户端错误上下文
为 HTTP 客户端构造失败统一保留操作上下文

补充配置错误文案回归测试
2026-09-09 10:57:04 +08:00
k88936 5ee2d940c1 修正 Raw 图片尺寸校验错误语义
尺寸校验失败改用请求参数无效语义

补充错误文案回归测试
2026-09-09 10:55:36 +08:00
k88936 c75a7725ce Merge remote-tracking branch 'origin/master' into feat/expose-image2edit
Project CI / Repository checks (pull_request) Successful in 2m51s
Project CI / Native shell tests (pull_request) Failing after 4m9s
Project CI / Backend tests (pull_request) Failing after 4m58s
Project CI / Frontend tests (pull_request) Successful in 3m46s
2026-09-09 10:42:22 +08:00
k88936 3a95715990 同步 Raw 图片编辑技术方案
Project CI / Native shell tests (pull_request) Failing after 6m36s
Project CI / Backend tests (pull_request) Failing after 14s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Successful in 4m22s
记录解码资源上限和阻塞线程策略
补充输出格式回退与成功追踪约定
2026-09-08 23:41:27 +08:00
k88936 ed86963de2 修正 Raw 图片追踪结果类型
明确输出数据集合类型
保持 API 编译检查通过
2026-09-08 23:38:25 +08:00
k88936 65e73eee02 补充 Raw 图片生成成功追踪
记录成功任务与输出数量
完善上游响应和失败阶段日志
2026-09-08 23:25:47 +08:00
k88936 6068eeda83 补充 Raw 图片编辑请求日志
记录上游响应状态与耗时
记录传输失败阶段和结构化上下文
2026-09-08 23:22:56 +08:00
k88936 0e244c64ea 统一 GPT Image 2 尺寸常量
集中维护像素边界和对齐规则
供生成与编辑路径共同引用
2026-09-08 22:11:35 +08:00
k88936 ad7c059603 遵守 Raw 图片请求截止时间
发送前裁剪有效超时预算
预算耗尽时提前返回结构化错误
2026-09-08 21:54:22 +08:00
k88936 55d2a393d1 完善 Raw 图片尺寸错误类型
为公开错误实现 Error trait
支持通用错误链处理
2026-09-08 21:52:15 +08:00
k88936 e7f66ce950 修正 Raw 图片传输失败阶段标记
区分请求发送和响应读取失败
让错误详情准确反映故障阶段
2026-09-08 20:03:13 +08:00
k88936 35dcfea3ef 修正 Raw 图片输出格式回退
忽略空值后再使用载荷级格式
补充空字符串和 null 回归测试
2026-09-08 19:56:41 +08:00
k88936 cea8bf77fa 减少 Raw 图片编辑请求拷贝
移动 multipart 选项字段而非克隆
降低掩码和字符串的峰值内存
2026-09-08 19:52:13 +08:00
k88936 0136bfe564 移出 Raw 图片同步解码
将请求预校验放入阻塞线程
避免占用 Tokio 异步工作线程
2026-09-08 19:48:58 +08:00
k88936 22785b4d7b 限制 Raw 图片解码资源
为 PNG 解码设置边长和分配上限
避免压缩图片膨胀导致内存耗尽
2026-09-08 19:45:02 +08:00
k88936 a86a2ee4d2 简化 Raw 图片编辑响应类型
移除无必要的 JSON Value 转换
保持响应结构由类型系统校验
2026-09-08 19:32:55 +08:00
k88936 b22eae2726 收紧 Raw 图片编辑输入契约
Project CI / Frontend tests (pull_request) Successful in 3m51s
Project CI / Native shell tests (pull_request) Successful in 20m20s
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / Backend tests (pull_request) Failing after 14s
新增 3840 边长、16 像素对齐、3:1 比例和总像素范围校验

要求 image 与 mask 为可完整解码的 PNG 并在扣费前拒绝非法输入

将 Raw 路由 JSON body limit 提升至 64 MiB

同步 Raw 图片编辑技术方案和边界测试
2026-09-08 18:18:58 +08:00
k88936 35d0db6377 修复 Raw 图片失败审计与响应格式
Project CI / Frontend tests (pull_request) Successful in 6m7s
Project CI / Native shell tests (pull_request) Successful in 19m41s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / Backend tests (pull_request) Failing after 9s
为 Raw provider 失败分支补齐结构化 failure audit 并接入 api-server 记录链

使用 VectorEngine 响应 output_format 生成返回图片 MIME 与扩展名

补充响应格式和审计字段定向测试并同步技术方案
2026-09-08 16:19:29 +08:00
k88936 f087ba3e2b 隔离 Raw 图片代理实现
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / Backend tests (pull_request) Failing after 14s
Project CI / Frontend tests (pull_request) Successful in 36m40s
Project CI / Native shell tests (pull_request) Successful in 54m1s
移除对现有图片编辑 client 与 curl transport 的复用

将 Raw GPT Image 2 请求、multipart 构造和响应解析收敛到独立文件

保持现有 editor 图片链路代码不变
2026-09-08 13:54:31 +08:00
k88936 7fc1b841be 收口 Raw 图片 base64 校验错误
客户端仅返回稳定的 base64 格式错误提示

补充测试避免泄露解码器内部诊断细节
2026-09-08 12:30:55 +08:00
k88936 a576fdcc37 移除未使用的 multipart 请求包装函数
无选项测试直接调用统一传输入口

避免生产构建保留未使用函数告警
2026-09-08 12:29:59 +08:00
k88936 4dcf38715b 补充图片编辑选项 multipart 测试
覆盖质量、背景、输出格式与 mask 文件字段

断言文件名、MIME 类型和字节内容均正确发送
2026-09-08 12:21:11 +08:00
k88936 d1f9f4e1ef 减少图片编辑 multipart 字节复制
将已克隆的选项按值传入阻塞传输函数

构造 mask 与参考图表单时直接移动字节缓冲
2026-09-08 12:16:00 +08:00
k88936 5836aaec26 简化图片编辑 multipart 请求分支
统一通过带选项的 multipart 传输函数发送请求

移除冗余包装函数调用与未使用导入
2026-09-08 12:10:56 +08:00
k88936 18d60feb04 补充 Raw 图片参数预检查
在计费前校验质量、背景和输出格式

规范化可选参数并保持单图请求契约
2026-09-08 10:40:09 +08:00
k88936 4e1316e272 实现 Raw GPT Image 2 图片编辑代理
新增受 Bearer 保护的 /api/raw/v1/images/edit JSON 路由

按单图 image 与可选 mask 转发 GPT Image 2 参数

固定 model 与 n 并返回仅含 data[].b64_json 的响应

复用钱包计费与退款边界并补充单元测试
2026-09-08 10:36:48 +08:00
k88936 302b4addae 补充 Raw GPT Image 2 代理方案
Project CI / Native shell tests (pull_request) Successful in 55m40s
Project CI / Backend tests (pull_request) Failing after 14s
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / Frontend tests (pull_request) Successful in 23m43s
明确 JSON 请求与 data 数组响应

记录预检查和钱包事务边界

拆分 api-server 与 platform-image 文件职责
2026-09-07 16:33:34 +08:00
204 changed files with 6528 additions and 11563 deletions
-2
View File
@@ -40,8 +40,6 @@ temp*build*/
/apps/ai-game-creator-shell/src-tauri/resources/codex/win-x64/codex-path/
/apps/ai-game-creator-shell/src-tauri/resources/codex/win-x64/codex-resources/
/apps/ai-game-creator-shell/src-tauri/resources/codex/win-x64/codex-package.json
/apps/ai-game-creator-shell/src-tauri/resources/plugins/
/plugins/agc-cocos-editor/native/payload/
/apps/ai-game-creator-shell/logs/
/apps/ai-game-creator-shell/.llm-drafts/
/apps/ai-game-creator-shell/game-creator.config.local.json
-4
View File
@@ -1,4 +0,0 @@
# resources/plugins 由 build.rs 从 plugins/ 复制生成,属于构建产物。
# 它在 dev 监听范围内,重新生成会让 Tauri dev 误判为源码改动而触发
# “构建 -> 监听 -> 再构建”的自触发循环。
resources/plugins/
@@ -4,11 +4,6 @@ import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
defaultEditorFeatures,
withDefaultCargoFeatures,
} from './cargo-features.mjs';
const appRoot = fileURLToPath(new URL('..', import.meta.url));
const defaultReleaseTarget = 'x86_64-pc-windows-msvc';
const releaseTarget =
@@ -164,30 +159,10 @@ export async function prepareReleaseVersion() {
return nextVersion;
}
export function buildTauriBuildArguments(
args = [],
target = releaseTarget,
platform = process.platform,
) {
const noBundle = args.includes('--no-bundle');
const targetIndex = args.indexOf('--target');
const explicitTarget =
targetIndex >= 0
? args[targetIndex + 1]
: args
.find((value) => value.startsWith('--target='))
?.slice('--target='.length);
const targetArgs = noBundle || explicitTarget ? [] : ['--target', target];
const features = defaultEditorFeatures(
explicitTarget || (noBundle ? platform : target),
);
return [
'build',
...withDefaultCargoFeatures([...targetArgs, ...args], features),
];
}
export function runTauriBuild(args = []) {
const noBundle = args.includes('--no-bundle');
const hasTarget = args.includes('--target');
const targetArgs = noBundle || hasTarget ? [] : ['--target', releaseTarget];
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const result = spawnSync(
npmCommand,
@@ -197,7 +172,9 @@ export function runTauriBuild(args = []) {
'exec',
'tauri',
'--',
...buildTauriBuildArguments(args),
'build',
...targetArgs,
...args,
],
{ cwd: appRoot, stdio: 'inherit', shell: process.platform === 'win32' },
);
@@ -1,24 +0,0 @@
/** 默认桌面能力;显式 feature 参数优先,不把应用参数当 Cargo 参数。 */
export function withDefaultCargoFeatures(argv, features) {
const separator = argv.indexOf('--');
const cargoArgs = separator < 0 ? argv : argv.slice(0, separator);
if (
!features.length ||
cargoArgs.some(
(value) =>
value === '--features' ||
value === '-f' ||
value.startsWith('--features=') ||
/^-f.+/u.test(value),
)
) {
return argv;
}
return [`--features=${features.join(',')}`, ...argv];
}
export function defaultEditorFeatures(target) {
return target === 'win32' || target.includes('windows')
? ['cocos-editor-execute']
: [];
}
@@ -1,49 +0,0 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { buildTauriBuildArguments } from './build-release.mjs';
import { withDefaultCargoFeatures } from './cargo-features.mjs';
test('Windows release includes the same editor feature as development', () => {
assert.deepEqual(
buildTauriBuildArguments([], 'x86_64-pc-windows-msvc', 'win32'),
[
'build',
'--features=cocos-editor-execute',
'--target',
'x86_64-pc-windows-msvc',
],
);
assert.deepEqual(
buildTauriBuildArguments(
['--no-bundle'],
'x86_64-pc-windows-msvc',
'linux',
),
['build', '--no-bundle'],
);
assert.deepEqual(
buildTauriBuildArguments(['--target=aarch64-apple-darwin']),
['build', '--target=aarch64-apple-darwin'],
);
});
test('explicit Cargo features override defaults in every supported spelling', () => {
for (const args of [
['--features', 'custom'],
['--features=custom'],
['-f', 'custom'],
['-fcustom'],
]) {
assert.deepEqual(
withDefaultCargoFeatures(args, ['cocos-editor-execute']),
args,
);
}
assert.deepEqual(
withDefaultCargoFeatures(
['--', '--features=app'],
['cocos-editor-execute'],
),
['--features=cocos-editor-execute', '--', '--features=app'],
);
});
@@ -108,6 +108,8 @@ const rustSharedContractSource = fs.readFileSync(
);
const allowedUncalledTauriCommands = [
'append_direct_project_conversation_message',
// TODO: Remove the retired binding command after the legacy runtime path is removed.
'bind_components',
'chat_with_game_creator_agent',
'check_ui_editor_font_glyph_coverage',
'create_ui_design_resource',
@@ -117,16 +119,6 @@ const allowedUncalledTauriCommands = [
'stop_local_game_preview_if_matches',
'start_game_creator_external_mcp',
'stop_game_creator_external_mcp',
'list_agc_plugins',
'list_agc_extensions',
'refresh_agc_plugins',
'start_agc_plugin',
'stop_agc_plugin',
'reload_agc_plugin',
'call_agc_plugin',
'read_agc_plugin_panel',
'set_agc_plugin_project_path',
'set_agc_plugin_enabled',
];
const sourceExtensions = new Set([
'.json',
@@ -1295,7 +1287,7 @@ if (tauriConfig.identifier !== 'world.genarrative.ai-game-creator') {
throw new Error('AI game creator shell identifier drifted');
}
const expectedBundledWindowsResources = {
const expectedBundledCodexResources = {
'resources/codex/win-x64/bin/codex.exe': 'codex/win-x64/bin/codex.exe',
'resources/codex/win-x64/bin/codex-code-mode-host.exe':
'codex/win-x64/bin/codex-code-mode-host.exe',
@@ -1309,7 +1301,6 @@ const expectedBundledWindowsResources = {
'codex/win-x64/codex-package.json',
'resources/codex/win-x64/NOTICE.md': 'codex/win-x64/NOTICE.md',
'resources/codex/win-x64/manifest.json': 'codex/win-x64/manifest.json',
'resources/plugins': 'plugins',
};
if (tauriConfig.bundle?.resources !== undefined) {
throw new Error(
@@ -1318,8 +1309,8 @@ if (tauriConfig.bundle?.resources !== undefined) {
}
assert.deepEqual(
windowsTauriConfig.bundle?.resources,
expectedBundledWindowsResources,
'AI game creator shell Windows Tauri config must bundle the complete pinned Codex resource set and Cocos bridge payload directory',
expectedBundledCodexResources,
'AI game creator shell Windows Tauri config must bundle the complete pinned Codex resource set',
);
if (windowsTauriConfig.bundle?.useLocalToolsDir !== true) {
throw new Error(
@@ -436,10 +436,6 @@ function spawnChild(command, args, options, spawnImpl = spawn) {
const child = spawnImpl(command, args, {
...options,
shell: useShell,
// npm.cmd and the Windows shell otherwise create a visible console for
// every service in the dev stack. Their stdout/stderr is already inherited
// by the launcher, so no separate terminal window is useful.
windowsHide: process.platform === 'win32' ? true : options.windowsHide,
// POSIX 下让每个长驻服务拥有独立进程组,退出时可以连同 npm、
// Node、Cargo 及其子进程一起清理,避免残留订阅任务持续刷日志。
detached: isPosix,
@@ -1,7 +1,6 @@
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { withDefaultCargoFeatures } from './cargo-features.mjs';
import {
readAgcDevEndpoint,
resolveAgcDevEndpoint,
@@ -44,24 +43,6 @@ function buildTauriArguments(argv, devUrl = readAgcDevEndpoint().url) {
];
}
// `agc_cocos_execute` 与 Cocos 编辑器适配器只在 `cocos-editor-execute` feature 下
// 注册。开发构建默认在 Windows 打开它,否则 Agent 的工具清单里根本没有该工具,
// 只能退化成改写脚本。可用 AGC_DEV_CARGO_FEATURES(逗号分隔)覆盖,传空串即关闭。
function readDevCargoFeatures(env = process.env) {
const override = env.AGC_DEV_CARGO_FEATURES;
if (override !== undefined) {
return override
.split(',')
.map((value) => value.trim())
.filter(Boolean);
}
return process.platform === 'win32' ? ['cocos-editor-execute'] : [];
}
function withDevCargoFeatures(argv, features = readDevCargoFeatures()) {
return withDefaultCargoFeatures(argv, features);
}
function spawnTauriCli(argv, { env = process.env } = {}) {
return spawnChild(process.execPath, [tauriCliPath, ...argv], {
cwd: appRoot,
@@ -125,10 +106,7 @@ async function runTauriDev(
shutdownRequested.then(() => false),
]);
if (!prepared || shutdownSignal) return 1;
const tauriArguments = buildTauriArguments(
withDevCargoFeatures(argv),
endpoint.url,
);
const tauriArguments = buildTauriArguments(argv, endpoint.url);
child = spawnCli(tauriArguments, {
env: withAgcDevEndpointEnv(endpoint),
});
@@ -215,7 +193,6 @@ export {
isDirectModuleExecution,
runTauriDev,
spawnTauriCli,
withDevCargoFeatures,
};
if (isDirectModuleExecution()) {
@@ -1,4 +0,0 @@
# resources/plugins 由 build.rs 从 plugins/ 复制生成,属于构建产物。
# 它在 dev 监听范围内,重新生成会让 Tauri dev 误判为源码改动而触发
# “构建 -> 监听 -> 再构建”的自触发循环。
resources/plugins/
-25
View File
@@ -733,20 +733,6 @@ dependencies = [
"error-code",
]
[[package]]
name = "cocos-editor-bridge"
version = "0.1.0"
dependencies = [
"cc",
"editor-adapter-api",
"reqwest 0.12.28",
"serde",
"serde_json",
"sha2",
"tungstenite",
"windows-sys 0.61.2",
]
[[package]]
name = "combine"
version = "4.6.7"
@@ -1219,14 +1205,6 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]]
name = "editor-adapter-api"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "either"
version = "1.16.0"
@@ -1731,8 +1709,6 @@ dependencies = [
"axum",
"base64 0.22.1",
"chromiumoxide",
"cocos-editor-bridge",
"editor-adapter-api",
"futures",
"getrandom 0.3.4",
"http",
@@ -4305,7 +4281,6 @@ dependencies = [
"cookie",
"cookie_store",
"encoding_rs",
"futures-channel",
"futures-core",
"futures-util",
"h2",
@@ -6,9 +6,6 @@ publish = false
[features]
default = []
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"]
[build-dependencies]
serde = { version = "1", features = ["derive"] }
@@ -22,8 +19,6 @@ ts-rs = "12.0.1"
typed_floats = { version = "1.0.7", features = ["serde"] }
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" }
base64 = "0.22"
axum = "0.8"
chromiumoxide = "0.9.1"
@@ -79,6 +74,14 @@ codegen-units = 256
lto = "off"
incremental = true
# Runner 启动阶段会对当前 Debug 可执行文件计算 SHA-256。仅优化密码学依赖,
# 保持业务代码的 Debug 编译速度,同时避免整份 Debug 构建因未优化 hash 热点而阻塞启动。
[profile.dev.package.sha2]
opt-level = 3
[profile.dev.package.digest]
opt-level = 3
[profile.test]
opt-level = 0
debug = 1
@@ -182,8 +182,6 @@ fn main() {
);
let manifest_path = manifest_dir.join("prompts/runtime/manifest.json");
stage_bundled_codex_cli(&manifest_dir);
stage_plugin_workspace(&manifest_dir);
stage_cocos_editor_payload(&manifest_dir);
let compiled = runtime_prompt_bundle::compile_manifest(&manifest_path)
.unwrap_or_else(|error| panic!("Prompt Bundle 编译失败:{error}"));
validate_seed_task_catalog(&compiled);
@@ -205,146 +203,3 @@ fn main() {
}
tauri_build::build()
}
#[cfg(windows)]
fn stage_cocos_editor_payload(manifest_dir: &std::path::Path) {
if std::env::var_os("CARGO_FEATURE_COCOS_EDITOR_INJECTION").is_none() {
return;
}
let out_dir = std::path::PathBuf::from(std::env::var_os("OUT_DIR").expect("OUT_DIR"));
let profile_dir = out_dir
.ancestors()
.find(|path| path.file_name().is_some_and(|name| name == "build"))
.and_then(|build_dir| build_dir.parent())
.expect("AGC Cargo profile directory not found");
let candidates = [
profile_dir.join("deps/cocos_editor_bridge.dll"),
profile_dir.join("cocos_editor_bridge.dll"),
];
let source = candidates
.iter()
.find(|path| path.is_file())
.unwrap_or_else(|| {
panic!(
"Cocos bridge native payload 未构建:{}",
candidates
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join("")
)
});
for destination in [
// 插件工作区里的 payload 是开发态与打包态的唯一真源。
manifest_dir
.join("../../../plugins/agc-cocos-editor/native/payload/cocos-editor-bridge.dll"),
// 随包资源目录与 tauri.windows.conf.json 的 `resources/plugins` 映射保持一致。
manifest_dir
.join("resources/plugins/agc-cocos-editor/native/payload/cocos-editor-bridge.dll"),
] {
std::fs::create_dir_all(destination.parent().expect("payload resource parent"))
.expect("创建 Cocos bridge payload 目录失败");
std::fs::copy(source, &destination).expect("复制 Cocos bridge native payload 失败");
}
println!("cargo:rerun-if-changed={}", source.display());
}
#[cfg(not(windows))]
fn stage_cocos_editor_payload(_manifest_dir: &std::path::Path) {}
/// 把 `plugins/` 工作区里的插件包随包映射到应用资源目录。
///
/// 只复制插件运行需要的清单、入口、面板和 native payload,不复制 native 源码、
/// Cargo target 目录或 node_modules。
#[cfg(windows)]
fn stage_plugin_workspace(manifest_dir: &std::path::Path) {
let repo_root = manifest_dir
.parent()
.and_then(|app_root| app_root.parent())
.and_then(|apps_dir| apps_dir.parent())
.expect("AGC 应用必须位于仓库 apps 目录下")
.to_path_buf();
let workspace = repo_root.join("plugins");
let destination_root = manifest_dir.join("resources/plugins");
std::fs::create_dir_all(&destination_root).expect("创建插件资源目录失败");
let entries = match std::fs::read_dir(&workspace) {
Ok(entries) => entries,
Err(_) => return,
};
for entry in entries.flatten() {
let plugin_root = entry.path();
if !plugin_root.is_dir() || !plugin_root.join("plugin.json").is_file() {
continue;
}
let name = entry.file_name();
let destination = destination_root.join(&name);
copy_plugin_file(
&plugin_root.join("plugin.json"),
&destination.join("plugin.json"),
);
for relative in [
std::path::PathBuf::from("src"),
std::path::PathBuf::from("panels"),
std::path::PathBuf::from("native/payload"),
] {
copy_plugin_tree(&plugin_root.join(&relative), &destination.join(&relative));
}
println!("cargo:rerun-if-changed={}", plugin_root.display());
}
}
#[cfg(windows)]
fn stage_plugin_file(source: &std::path::Path, destination: &std::path::Path) {
let Ok(bytes) = std::fs::read(source) else {
return;
};
if std::fs::read(destination).is_ok_and(|existing| existing == bytes) {
return;
}
if let Some(parent) = destination.parent() {
std::fs::create_dir_all(parent).expect("创建插件资源目录失败");
}
std::fs::write(destination, bytes).expect("复制插件资源失败");
}
#[cfg(windows)]
fn copy_plugin_tree(source: &std::path::Path, destination: &std::path::Path) {
let entries = match std::fs::read_dir(source) {
Ok(entries) => entries,
Err(_) => return,
};
for entry in entries.flatten() {
let target = destination.join(entry.file_name());
let path = entry.path();
if path.is_dir() {
let name = entry.file_name();
let name = name.to_string_lossy();
if matches!(name.as_ref(), "target" | "node_modules" | ".git") {
continue;
}
std::fs::create_dir_all(&target).expect("创建插件资源目录失败");
copy_plugin_tree(&path, &target);
} else {
// 测试文件不随包分发。
let name = entry.file_name();
let name = name.to_string_lossy();
if name.contains(".test.") {
continue;
}
stage_plugin_file(&path, &target);
}
}
}
#[cfg(windows)]
fn copy_plugin_file(source: &std::path::Path, destination: &std::path::Path) {
if !source.is_file() {
return;
}
std::fs::create_dir_all(destination.parent().expect("插件资源父目录"))
.expect("创建插件资源目录失败");
std::fs::copy(source, destination).expect("复制插件资源失败");
}
#[cfg(not(windows))]
fn stage_plugin_workspace(_manifest_dir: &std::path::Path) {}
@@ -305,35 +305,6 @@ fn game_creator_codex_app_server_error_detail_indicates_auth_failure(
|| detail.contains("http 403")
}
fn game_creator_codex_app_server_error_detail(error: &serde_json::Value) -> String {
let Some(error) = error.as_object() else {
return String::new();
};
["message", "additionalDetails", "code"]
.into_iter()
.filter_map(|field| error.get(field).and_then(serde_json::Value::as_str))
.collect::<Vec<_>>()
.join(" ")
.to_ascii_lowercase()
}
fn game_creator_codex_app_server_error_detail_indicates_stream_requirement(
error: &serde_json::Value,
) -> bool {
let detail = game_creator_codex_app_server_error_detail(error);
detail.contains("stream must be set to true")
|| detail.contains("stream=true")
|| detail.contains("stream is required")
}
fn game_creator_codex_app_server_error_detail_indicates_timeout(error: &serde_json::Value) -> bool {
let detail = game_creator_codex_app_server_error_detail(error);
detail.contains("timed out")
|| detail.contains("timeout")
|| detail.contains("request deadline exceeded")
|| detail.contains("deadline exceeded")
}
fn game_creator_codex_app_server_error_detail_indicates_request_too_large(
error: &serde_json::Value,
) -> bool {
@@ -391,15 +362,6 @@ fn game_creator_codex_app_server_failed_turn_error(
if game_creator_codex_app_server_error_detail_indicates_request_too_large(error) {
return game_creator_codex_app_server_error_kind("request-too-large");
}
if game_creator_codex_app_server_error_detail_indicates_stream_requirement(error) {
return game_creator_codex_app_server_error_kind("stream-required");
}
if game_creator_codex_app_server_error_detail_indicates_timeout(error) {
return platform_llm::LlmError::Connectivity {
attempts: 1,
message: "Codex app-server 上游请求超时".to_string(),
};
}
if game_creator_codex_app_server_error_detail_indicates_auth_failure(error) {
return game_creator_codex_app_server_error_kind("unauthorized");
}
@@ -1108,7 +1070,6 @@ fn game_creator_codex_app_server_pool_key(
"skillPackIdentity": skill_pack_identity,
"clientSkillIdentity": client_skill_identity,
"clientMcpIdentity": client_mcp_identity,
"builtinPluginTools": crate::builtin_plugins::available_agent_tools(),
"controlledWebSearch": llm.web_search_enabled,
"directToolBridgeProtocol": if workspace_mode == CodexAppServerWorkspaceMode::DirectProject { DIRECT_TOOL_BRIDGE_PROTOCOL } else { "disabled" },
"providerProxyProtocol": if workspace_mode == CodexAppServerWorkspaceMode::DirectProject { CODEX_PROVIDER_PROXY_PROTOCOL } else { "disabled" },
@@ -2502,12 +2463,10 @@ impl CodexAppServerConnection {
// Codex's default provider in Debug builds.
self.inner._provider_proxy.is_some() || !llm.api_key.trim().is_empty(),
);
let result = match self.request("thread/start", params).await {
Ok(result) => result,
Err(error) => {
return Err(platform_llm::LlmError::Transport(error));
}
};
let result = self
.request("thread/start", params)
.await
.map_err(platform_llm::LlmError::Transport)?;
let thread_id = result
.pointer("/thread/id")
.and_then(serde_json::Value::as_str)
@@ -4850,37 +4809,6 @@ mod tests {
}
}
#[test]
fn codex_app_server_failed_turn_maps_stream_and_timeout_details() {
let stream_error = game_creator_codex_app_server_failed_turn_error(&serde_json::json!({
"status": "failed",
"error": {
"message": "Stream must be set to true",
"codexErrorInfo": "other"
}
}));
assert_eq!(
stream_error,
platform_llm::LlmError::InvalidRequest(
"codex-app-server-error:stream-required".to_string()
)
);
let timeout_error = game_creator_codex_app_server_failed_turn_error(&serde_json::json!({
"status": "failed",
"error": {
"message": "provider request timed out",
"codexErrorInfo": "other"
}
}));
assert_eq!(
timeout_error,
platform_llm::LlmError::Connectivity {
attempts: 1,
message: "Codex app-server 上游请求超时".to_string()
}
);
}
#[test]
fn codex_app_server_failed_turn_maps_insufficient_mud_points_to_stable_upstream_error() {
for detail in [
@@ -5122,42 +5050,6 @@ while IFS= read -r line; do :; done
assert_ne!(disabled, enabled);
}
#[cfg(feature = "cocos-editor-execute")]
#[test]
fn codex_app_server_pool_key_tracks_builtin_plugin_switch() {
let _guard = crate::builtin_plugins::test_lock();
let config = tempfile::tempdir().unwrap();
crate::builtin_plugins::initialize(config.path()).unwrap();
let key = || {
game_creator_codex_app_server_pool_key(
&test_llm(),
"codex-cli 0.147.0",
&test_snapshot(),
"credential",
CodexAppServerWorkspaceMode::DirectProject,
)
};
crate::builtin_plugins::set_enabled(
crate::builtin_plugins::AGC_COCOS_EDITOR_PLUGIN_ID,
false,
)
.unwrap();
let disabled = key();
crate::builtin_plugins::set_enabled(
crate::builtin_plugins::AGC_COCOS_EDITOR_PLUGIN_ID,
true,
)
.unwrap();
let enabled = key();
assert_ne!(disabled, enabled);
crate::builtin_plugins::set_enabled(
crate::builtin_plugins::AGC_COCOS_EDITOR_PLUGIN_ID,
false,
)
.unwrap();
assert_eq!(disabled, key());
}
#[test]
fn direct_file_change_approval_is_limited_to_workspace() {
let temp = tempfile::tempdir().expect("temp dir");
@@ -1,4 +1,3 @@
use super::runtime_actions::acquire_game_creator_agent_runtime_project_write_lock_with_wait;
use crate::config::prepare_game_creator_private_path_for_read;
use crate::project::{
append_jsonl_line_unlocked, enforce_project_permission_policy, project_append_lock_for,
@@ -205,15 +204,7 @@ fn append_direct_project_history_item_at_with_user_policy(
if !allow_user_item && is_direct_project_codex_user_item(item) {
return Ok(());
}
// DirectProject 历史与 `agc_write_file` 共用项目写锁。文件写入在锁内要跑 Windows
// 私有路径准备与原子替换,现场实测一次 2.6KB 写入占锁 5.5 秒;零等待取锁会让
// 流式历史落盘在写文件期间直接失败,并把整轮判成“项目正在被其他写操作占用”
// (持锁方 commandId=direct-codex.file.write、ownerIsSelf=true)。这里与其它写入口
// 保持同一档有界等待;主路径已在阻塞线程池中执行。
let _project_lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
"conversation.write",
)?;
let _project_lock = crate::project::acquire_project_write_lock(root, "conversation.write")?;
let path = history_path(root);
let history_exists =
prepare_game_creator_private_path_for_read(&path, false, "DirectProject 历史")?;
@@ -438,50 +429,6 @@ mod tests {
assert_eq!(items, vec![item]);
}
#[test]
fn append_waits_for_a_same_process_project_writer() {
// 用 canonical 临时根:Windows 上 `%TEMP%` 的 8.3 短路径会让私有路径所有者
// 校验把测试目录判成“不属于当前用户”。
let temp_root = std::env::temp_dir()
.canonicalize()
.unwrap_or_else(|_| std::env::temp_dir());
let root = temp_root.join(format!(
"genarrative-agc-history-wait-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|value| value.as_millis())
.unwrap_or_default()
));
std::fs::create_dir_all(&root).expect("create project root");
crate::init_local_game_project_at(&root, "history-wait", "历史等待").expect("init project");
// 模拟 `agc_write_file`:它持锁期间历史落盘必须排队等待,而不是零等待失败后
// 把整轮判成“项目正在被其他写操作占用”。
let holder = crate::project::acquire_project_write_lock(&root, "direct-codex.file.write")
.expect("hold project write lock");
let worker_root = root.clone();
let worker = std::thread::spawn(move || {
append_direct_project_history_item_at(
&worker_root,
&serde_json::json!({
"type": "message",
"role": "assistant",
"id": "waits-for-writer",
"content": [{"type": "output_text", "text": "排队等待"}]
}),
)
});
std::thread::sleep(std::time::Duration::from_millis(300));
drop(holder);
worker
.join()
.expect("append worker")
.expect("history append must wait for the writer");
let items = read_direct_project_history_items_at(&root).expect("read history");
assert_eq!(items.len(), 1);
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn codex_user_echo_is_filtered_but_agc_user_message_is_persisted() {
let root = tempfile::tempdir().expect("temp project");
File diff suppressed because one or more lines are too long
@@ -54,8 +54,6 @@ struct DirectToolBridgeState {
regeneration_gate: tokio::sync::Mutex<()>,
resource_generation_gate: tokio::sync::Mutex<()>,
image_generation_gate: tokio::sync::Mutex<()>,
#[cfg(all(windows, feature = "cocos-editor-execute"))]
cocos_execute_uncertain: tokio::sync::Mutex<bool>,
}
#[derive(Default)]
@@ -689,8 +687,6 @@ fn direct_tool_bridge_state_with_search(
regeneration_gate: tokio::sync::Mutex::new(()),
resource_generation_gate: tokio::sync::Mutex::new(()),
image_generation_gate: tokio::sync::Mutex::new(()),
#[cfg(all(windows, feature = "cocos-editor-execute"))]
cocos_execute_uncertain: tokio::sync::Mutex::new(false),
})
}
@@ -1481,28 +1477,12 @@ fn bridge_write_file(root: &Path, arguments: &Value) -> Value {
// 而 `file.write / file.patch / file.delete` 等写入口用的是约 10 秒有界等待。
// 这是用户直接触发、失败即整轮无法落盘的项目写入通道,必须和其它写入口同语义:
// 短暂重叠排队等成功,只有预算耗尽才报出带持锁方身份的错误。
let acquire_started = std::time::Instant::now();
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
"direct-codex.file.write",
)?;
let lock_wait_ms = acquire_started.elapsed().as_millis();
let write_started = std::time::Instant::now();
let written = write_local_project_file_at(root, &path, content)?;
let write_ms = write_started.elapsed().as_millis();
let revision_started = std::time::Instant::now();
let revision = advance_agent_runtime_project_revision_locked(root)?;
// 现场一次 2.6KB 写入实测 5.5 秒。只在明显偏慢时记账,正常写入不刷日志。
if lock_wait_ms + write_ms > 200 {
app_log!(
"direct.file.write.timing path={} bytes={} lockWaitMs={} writeMs={} revisionMs={}",
written.path,
content.len(),
lock_wait_ms,
write_ms,
revision_started.elapsed().as_millis()
);
}
Ok::<_, String>(json!({
"status": "completed",
"path": written.path,
@@ -2343,122 +2323,11 @@ async fn bridge_web_search_at(root: &Path, arguments: &Value, search_url: &str)
}
}
#[cfg(all(windows, feature = "cocos-editor-execute"))]
async fn bridge_cocos_execute(state: &DirectToolBridgeState, arguments: &Value) -> Value {
if !crate::builtin_plugins::cocos_editor_agent_tool_available() {
return bridge_tool_result(
"Cocos Creator 插件已禁用,agc_cocos_execute 不可用".to_string(),
Vec::new(),
true,
);
}
let prepared = (|| {
bridge_reject_unknown_fields(arguments, &["code"])?;
enforce_project_permission_policy(&state.root, "cocos.editor.execute")?;
let code = arguments
.get("code")
.and_then(Value::as_str)
.ok_or_else(|| "code 必须是 JavaScript 函数体".to_string())?;
cocos_editor_bridge::validate_execute_code(code).map_err(|error| error.to_string())?;
Ok::<_, String>(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 mut uncertain = state.cocos_execute_uncertain.lock().await;
if *uncertain {
return bridge_tool_result(
json!({
"status": "needs-reconciliation", "retryAllowed": false,
"message": "先前 Cocos execute 结果待核对,当前 bridge 不再发送执行命令"
})
.to_string(),
Vec::new(),
true,
);
}
let root = state.root.clone();
let result = tokio::task::spawn_blocking(move || {
// Cocos execute talks to the already-open Creator process through its
// validated Inspector/pipe bridge. It does not mutate AGC's project
// files or manifest, so it must not wait on `.agent/project.lock`.
// File-writing tools keep their own project lock separately.
if !crate::builtin_plugins::cocos_editor_agent_tool_available() {
return Err(cocos_editor_bridge::BridgeError::InvalidInput(
"Cocos Creator 插件已禁用".to_string(),
));
}
cocos_editor_bridge::execute_cocos_editor_code_for_project(
root.to_string_lossy().as_ref(),
&code,
cocos_editor_bridge::DEFAULT_COMMAND_TIMEOUT_MS,
)
})
.await;
match result {
Ok(Ok(response)) => {
let is_error = !response.ok;
let text = json!({
"status": if response.ok { "completed" } else { "failed" },
"requestId": response.request_id,
"result": response.result,
"error": response.error,
})
.to_string();
bridge_tool_result(
redact_agent_runtime_project_paths(&state.root, &text, 32_000),
Vec::new(),
is_error,
)
}
failed => {
let (is_uncertain, error) = match failed {
Ok(Err(error)) => (
matches!(
&error,
cocos_editor_bridge::BridgeError::ExecutionUncertain(_)
),
error.to_string(),
),
Err(_) => (
true,
"Cocos execute worker 退出,执行结果需要核对".to_string(),
),
Ok(Ok(_)) => unreachable!(),
};
*uncertain = is_uncertain;
bridge_tool_result(
json!({
"status": if is_uncertain { "needs-reconciliation" } else { "failed" },
"retryAllowed": !is_uncertain,
"message": redact_agent_runtime_error(&state.root, &error, 480),
})
.to_string(),
Vec::new(),
true,
)
}
}
}
async fn handle_direct_tool_bridge(
State(state): State<Arc<DirectToolBridgeState>>,
Json(request): Json<DirectToolBridgeRequest>,
) -> Json<Value> {
let result = match request.tool.as_str() {
// 隔离 MCP 只取工具名,不接触真实 AppData 或读取权限。
"builtin.plugins.tools" => bridge_tool_result(
json!({"tools": crate::builtin_plugins::available_agent_tools()}).to_string(),
Vec::new(),
false,
),
"taonier_prepare_game_art" => bridge_prepare_game_art(&state, &request.arguments).await,
"agc_generate_image" => bridge_generate_image(&state, &request.arguments).await,
"agc_edit_image" => bridge_edit_image(&state, &request.arguments).await,
@@ -2466,8 +2335,6 @@ async fn handle_direct_tool_bridge(
bridge_list_registered_assets(&state.root, &request.arguments)
}
"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,
"agc_write_file" => {
bridge_write_file_in_blocking_pool(state.root.clone(), request.arguments).await
}
@@ -2513,11 +2380,14 @@ pub(crate) async fn start_direct_tool_bridge(
.route(&route, post(handle_direct_tool_bridge))
.layer(DefaultBodyLimit::max(DIRECT_TOOL_BRIDGE_MAX_REQUEST_BYTES))
.with_state(Arc::clone(&state));
let url = format!("http://127.0.0.1:{}{route}", address.port());
let task = tokio::spawn(async move {
let _ = axum::serve(listener, app).await;
});
Ok(DirectToolBridge { url, state, task })
Ok(DirectToolBridge {
url: format!("http://127.0.0.1:{}{route}", address.port()),
state,
task,
})
}
#[cfg(test)]
@@ -74,36 +74,11 @@ 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")) {
// 每次 tools/list 询问绑定的宿主;失败时不广告可选插件工具。
if let Ok(result) = tokio::time::timeout(
std::time::Duration::from_secs(5),
call_client_tool_bridge("builtin.plugins.tools", &json!({})),
)
.await
{
if result["isError"] == false {
let availability = result
.pointer("/content/0/text")
.and_then(Value::as_str)
.and_then(|text| serde_json::from_str::<Value>(text).ok());
cocos_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_COCOS_EDITOR_TOOL_NAME)
});
}
}
}
direct_tools_mcp_specs_for(controlled_web_search_enabled(), cocos_editor_available)
fn direct_tools_mcp_specs() -> Value {
direct_tools_mcp_specs_for(controlled_web_search_enabled())
}
fn direct_tools_mcp_specs_for(controlled_web_search: bool, _cocos_editor_available: bool) -> Value {
fn direct_tools_mcp_specs_for(controlled_web_search: bool) -> Value {
let tools = vec![
json!({
"name": "client.session.info",
@@ -456,19 +431,6 @@ fn direct_tools_mcp_specs_for(controlled_web_search: bool, _cocos_editor_availab
}),
];
let mut tools = tools;
#[cfg(all(windows, feature = "cocos-editor-execute"))]
if _cocos_editor_available {
tools.push(json!({
"name": "agc_cocos_execute",
"description": "在当前项目已连接的 Cocos Creator 主进程执行 JavaScript 函数体,支持 await 和 return。宿主绑定项目和目标进程,只提交 code。结果待核对或超时后禁止自动重发;使用 Editor.Message 调用 Creator API。",
"inputSchema": {
"type": "object",
"properties": { "code": { "type": "string", "minLength": 1, "maxLength": cocos_editor_bridge::MAX_EXECUTE_CODE_BYTES } },
"required": ["code"],
"additionalProperties": false
}
}));
}
if controlled_web_search {
tools.push(json!({
"name": "agc_web_search",
@@ -541,21 +503,6 @@ fn validate_write_file_arguments(arguments: &Value) -> Result<(), String> {
normalize_relative_path(&path).map(|_| ())
}
#[cfg(all(windows, feature = "cocos-editor-execute"))]
async fn call_agc_cocos_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 必须是 JavaScript 函数体".to_string())?;
cocos_editor_bridge::validate_execute_code(code).map_err(|error| error.to_string())
});
if let Err(error) = validated {
return mcp_tool_result(error, Vec::new(), true);
}
call_client_tool_bridge("agc_cocos_execute", arguments).await
}
fn mcp_success(id: Value, result: Value) -> Value {
json!({ "jsonrpc": "2.0", "id": id, "result": result })
}
@@ -1388,12 +1335,7 @@ fn external_mcp_record_response(root: &Path, arguments: &Value) -> Value {
true,
);
}
// 与 DirectProject 历史落盘同一档有界等待:`agc_write_file` 持锁期间可能持续数秒,
// 零等待取锁会让 Codex 返回记录直接丢失。调用方已把本函数放进阻塞线程池。
let _project_lock = match acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
"conversation.write",
) {
let _project_lock = match acquire_project_write_lock(root, "conversation.write") {
Ok(lock) => lock,
Err(error) => {
return mcp_tool_result(format!("项目对话锁不可用:{error}"), Vec::new(), true)
@@ -1614,7 +1556,7 @@ async fn handle_direct_tools_mcp_request(root: &Path, request: Value) -> Option<
))
}
}
"tools/list" => Some(mcp_success(id, direct_tools_mcp_specs().await)),
"tools/list" => Some(mcp_success(id, direct_tools_mcp_specs())),
"tools/call" => {
let tool = request
.pointer("/params/name")
@@ -1627,29 +1569,12 @@ async fn handle_direct_tools_mcp_request(root: &Path, request: Value) -> Option<
let result = match tool {
"client.session.info" => external_mcp_session_info(root),
"conversation.record_codex_response" => {
// 取锁等待是同步轮询(最多约 10 秒),必须放到阻塞线程池,
// 否则会占住 runtime worker。
let journal_root = root.to_path_buf();
let journal_arguments = arguments.clone();
match tokio::task::spawn_blocking(move || {
external_mcp_record_response(&journal_root, &journal_arguments)
})
.await
{
Ok(result) => result,
Err(error) => mcp_tool_result(
format!("Codex 返回记录任务未返回:{error}"),
Vec::new(),
true,
),
}
external_mcp_record_response(root, &arguments)
}
"conversation.list" => external_mcp_conversation_list(root, &arguments),
"conversation.read" => external_mcp_conversation_read(root, &arguments),
"agc_read_skill_resource" => call_agc_read_skill_resource(&arguments),
"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,
"taonier_prepare_game_art" => call_taonier_prepare_game_art(&arguments).await,
"agc_generate_image" => call_agc_generate_image(&arguments).await,
"agc_edit_image" => call_agc_edit_image(&arguments).await,
@@ -1842,119 +1767,6 @@ pub(crate) fn stop_game_creator_external_mcp() -> Result<(), String> {
#[cfg(test)]
mod tests {
use super::*;
#[cfg(all(windows, feature = "cocos-editor-execute"))]
#[test]
fn builtin_mcp_process_probe() {
let Ok(expected) = std::env::var("AGC_MCP_TEST_COCOS_EXPECTED") else {
return;
};
assert!(crate::game_creator_runtime_config_dir().is_none());
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let specs = runtime.block_on(direct_tools_mcp_specs());
assert_eq!(
specs["tools"]
.as_array()
.unwrap()
.iter()
.any(|tool| tool["name"] == "agc_cocos_execute"),
expected == "true"
);
}
#[cfg(all(windows, feature = "cocos-editor-execute"))]
#[tokio::test]
async fn builtin_tools_follow_host_switch_in_isolated_mcp_processes() {
let _guard = crate::builtin_plugins::test_lock();
let config = tempfile::tempdir().unwrap();
crate::builtin_plugins::initialize(config.path()).unwrap();
let project = crate::tests::canonical_test_tempdir("builtin-mcp-project-");
// 本用例只访问可用工具摘要和禁用入口,无需初始化完整游戏项目。
std::fs::create_dir_all(project.path().join(".agent")).unwrap();
std::fs::write(project.path().join(".agent/manifest.json"), "{}").unwrap();
let bridge =
super::super::direct_tool_bridge::start_direct_tool_bridge(project.path(), false)
.await
.unwrap();
for enabled in [false, true, false, true] {
crate::builtin_plugins::set_enabled(
crate::builtin_plugins::AGC_COCOS_EDITOR_PLUGIN_ID,
enabled,
)
.unwrap();
// 同一个 MCP 服务重复 tools/list,同时覆盖原生函数永久缓存的切换。
let specs = EXTERNAL_MCP_BRIDGE_URL
.scope(bridge.url().to_string(), direct_tools_mcp_specs())
.await;
assert_eq!(
specs["tools"]
.as_array()
.unwrap()
.iter()
.any(|tool| tool["name"] == "agc_cocos_execute"),
enabled
);
assert_eq!(
crate::agent_native_tools::native_runtime_function_name(
crate::builtin_plugins::AGC_COCOS_EDITOR_TOOL_NAME
)
.is_some(),
enabled
);
if !enabled {
let response = EXTERNAL_MCP_BRIDGE_URL
.scope(
bridge.url().to_string(),
call_agc_cocos_execute(&json!({"code":"return 1;"})),
)
.await;
assert_eq!(response["isError"], true);
assert!(response.to_string().contains("插件已禁用"));
}
// 子进程没有真实 AppData,必须只从绑定宿主获取可用性。
let url = bridge.url().to_string();
let result = tokio::task::spawn_blocking(move || {
std::process::Command::new(std::env::current_exe().unwrap())
.args([
"--exact",
"agent::direct_tools_mcp::tests::builtin_mcp_process_probe",
"--nocapture",
])
.env(DIRECT_TOOL_BRIDGE_URL_ENV, url)
.env("AGC_MCP_TEST_COCOS_EXPECTED", enabled.to_string())
.output()
.unwrap()
})
.await
.unwrap();
assert!(
result.status.success(),
"{} {}",
String::from_utf8_lossy(&result.stdout),
String::from_utf8_lossy(&result.stderr)
);
assert!(
String::from_utf8_lossy(&result.stdout).contains("1 passed"),
"child probe must run"
);
}
std::fs::write(config.path().join("extensions/builtin-plugins.json"), "{").unwrap();
let specs = EXTERNAL_MCP_BRIDGE_URL
.scope(bridge.url().to_string(), direct_tools_mcp_specs())
.await;
assert!(!specs.to_string().contains("agc_cocos_execute"));
drop(bridge);
let specs = EXTERNAL_MCP_BRIDGE_URL
.scope(
"http://127.0.0.1:1/tool-unavailable".to_string(),
direct_tools_mcp_specs(),
)
.await;
assert!(!specs.to_string().contains("agc_cocos_execute"));
}
use std::io::{Read, Write};
#[tokio::test]
@@ -2030,7 +1842,7 @@ mod tests {
DIRECT_TOOLS_MCP_MAX_REQUEST_BYTES > DIRECT_TOOLS_MCP_MAX_WRITE_CONTENT_BYTES + 1024,
"MCP request envelope must fit the advertised file-write payload"
);
let specs = direct_tools_mcp_specs_for(false, true);
let specs = direct_tools_mcp_specs_for(false);
let names = specs["tools"]
.as_array()
.expect("tool array")
@@ -2057,11 +1869,6 @@ mod tests {
"agc_remove_background",
"agc_browser_playtest",
]
.into_iter()
.chain(
cfg!(all(windows, feature = "cocos-editor-execute")).then_some("agc_cocos_execute")
)
.collect::<Vec<_>>()
);
let serialized = specs.to_string();
assert!(!serialized.contains("agc_web_search"));
@@ -2168,7 +1975,7 @@ mod tests {
#[test]
fn tool_catalog_adds_controlled_web_search_only_when_enabled() {
let specs = direct_tools_mcp_specs_for(true, true);
let specs = direct_tools_mcp_specs_for(true);
let search = specs["tools"]
.as_array()
.expect("tool array")
@@ -1156,6 +1156,10 @@ mod tests {
assert!(with_canvas.contains("根据当前玩法需求编写规格和界面建议"));
assert!(with_canvas.contains("用途、数量、输出路径、尺寸、参考资源和是否需要 spritesheet"));
assert!(with_canvas.contains("再调用 canvas.asset_generate"));
assert!(with_canvas.contains("调用 canvas.asset_generate"));
assert!(with_canvas.contains("不要使用固定图片合同"));
assert!(with_canvas.contains("不修改 game/index.html"));
assert!(!with_canvas.contains("不调用 canvas.asset_generate"));
}
#[test]
@@ -801,7 +801,7 @@ fn agent_runtime_action_receipt_safe_detail_with_owner(
let initial_step = route.get("initialStep")?.as_str()?;
let render_mode = route.get("renderMode")?.as_str()?;
if resource_id.is_empty()
|| initial_step != "visual-binding"
|| initial_step != "asset-separation"
|| render_mode != "final-preview"
{
return None;
@@ -1606,17 +1606,6 @@ 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!(
"codeChars={} · codeSha256={:x}",
chars(&["code"]),
Sha256::digest(
input
.get("code")
.and_then(serde_json::Value::as_str)
.unwrap_or_default()
.as_bytes()
)
),
"preview.validate" => {
let viewports = input
.get("viewports")
@@ -362,16 +362,6 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_
observe_agent_runtime_limited_command(root, agent_id, run_id, &action.input)
}
"preview.start" => observe_agent_runtime_preview_start(root, agent_id, run_id),
"cocos.editor.execute" => observe_agent_runtime_project_snapshot_with_lock(
root,
agent_id,
run_id,
action,
&action_fingerprint,
pending_action,
true,
|| observe_agent_runtime_cocos_editor_execute(root, action, pending_action),
),
"preview.validate" => {
observe_agent_runtime_preview_validate(
root,
@@ -95,8 +95,6 @@ pub(in crate::agent) fn game_creator_agent_runtime_tool_command_id(
"command.stdin" => Some("command.stdin"),
"command.terminate" => Some("command.terminate"),
"command.run_limited" => Some("command.run_limited"),
#[cfg(feature = "cocos-editor-execute")]
"cocos.editor.execute" => Some("cocos.editor.execute"),
"preview.start" => Some("preview.start"),
"preview.validate" => Some("preview.validate"),
"image.inspect" => Some("image.inspect"),
@@ -17,7 +17,7 @@ mod canvas_asset_kind_contract_tests {
}
pub(crate) fn agent_runtime_executable_tools() -> Vec<&'static str> {
let mut tools = vec![
vec![
GAME_CREATOR_USER_INPUT_REQUEST_TOOL,
"memory.read",
"memory.write",
@@ -63,13 +63,7 @@ pub(crate) fn agent_runtime_executable_tools() -> Vec<&'static str> {
"agent.schedule_ready",
"agent.action_history",
"agent.run_status",
];
// 内置插件被用户禁用后,对应 Runtime 工具不再进入工具目录、Agent 上下文
// 和工具策略快照。
if crate::builtin_plugins::cocos_editor_agent_tool_available() {
tools.push(crate::builtin_plugins::AGC_COCOS_EDITOR_TOOL_NAME);
}
tools
]
}
pub(crate) fn agent_runtime_native_executable_tools() -> Vec<&'static str> {
@@ -1908,7 +1908,7 @@ mod tests {
)
.expect("first persist");
rewind_session_keep_gdd_file(&root, &session);
let hydrated = hydrate_planning_session_v2_sync(
let hydrated = hydrate_planning_session_v2(
root.to_string_lossy().to_string(),
Some(session.session_id.clone()),
)

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