Compare commits

..

103 Commits

Author SHA1 Message Date
k88936 dc5a5a4a62 修复合并后 CI 失败:退役命令的残留清单与用例
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m21s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m57s
Project CI / Backend tests (pull_request) Successful in 4m0s
Project CI / Frontend tests (pull_request) Successful in 1m53s
Project CI / Native shell tests (pull_request) Successful in 5m51s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 9m13s
Project CI / Repository checks (pull_request) Successful in 2m1s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 9m20s
Project CI / AI game creator shell web tests (pull_request) Failing after 2m40s
- scripts/check-config.mjs 的 native-only 白名单删除已退役的 bind_components 与 create_ui_design_resource,两端原生能力检查重新自洽
- appSurface 的 UI 原型入口用例改断言 create_ui_design_doc_from_images,并按新契约补齐 relativePath / imageIds 返回形状
- 验证:npm run typecheck(含 skill-pack 与 check-config)、npx vitest run tests/appSurface.test.ts(211 passed / 9 skipped)
2026-09-24 14:21:14 +08:00
k88936 2873bed732 合并 origin/master 到 feat/ui-editor-v3
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m27s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m5s
Project CI / Native shell tests (pull_request) Failing after 1m47s
Project CI / Backend tests (pull_request) Successful in 3m53s
Project CI / Frontend tests (pull_request) Successful in 1m38s
Project CI / AI game creator shell web tests (pull_request) Failing after 38s
Project CI / Repository checks (pull_request) Failing after 1m33s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 9m0s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 9m2s
- 合入 master 179 个提交(后台管理页、游戏发行入口、AGC 运行页签、编译 warning 清理等)
- policy.rs 采用 master 版本:删除无调用方的 autonomous_design_foundation_command_is_allowed 重复名单,design-foundation 权限以 tool_policy_snapshot.rs 为准
- native-tools.json 保留双方结果:master 的描述文本 + 分支删除已退役的 ui.workflow.run.description
- useUiEditorPage.ts 保留分支的 save / saveAndGenerateCode 状态返回契约,并接上 master 的 beginUiSaveAnalytics 与 saveSource: 'auto' 埋点
- 验证:cargo check --bin genarrative-ai-game-creator-shell、npm run check:rustfmt、tsc --noEmit、vitest tests/uiEditorPage.test.ts + tests/clientAnalytics.test.tsx(47 passed)
2026-09-24 13:53:34 +08:00
k88936 a39b322af6 将 UI 编辑器 Agent 工具模块由 design_doc 重命名为 agent_tools
- 重命名 src-tauri/src/ui_editor/design_doc/{checkpoint,creation,mod,run_workflow,test_support}.rs 与 steps/** 到 ui_editor/agent_tools/
- 更新 main.rs、ui_editor/mod.rs、agent/runtime_tools/ui_design_doc.rs 中的模块路径引用
- 更新 steps/recognize.rs 与 steps/separate/mod.rs 内部对 agent_tools 夹具的引用
- 更新 tests/project.rs 注释中的 creation.rs 与 next_ui_design_path 模块路径
- 更新 agent_tools/test_support.rs 模块注释为 agent_tools 单测共用夹具
- 更新技术方案《UI编辑器代码地图与模块职责》《UI编辑器Agent工具化重写》的模块树与落地顺序路径
- 更新 shared-memory decision-log 中该次重写的测试过滤器为 agent_tools
2026-09-24 13:34:14 +08:00
k88936 0262e82572 把 write-back 步骤拆成独立模块
新增 steps/write_back.rs,承载保存 State、追加 write-back / outdated 行与漂移文案
把 rebuild_target_state 从 run_workflow.rs 移到 steps/mod.rs,作为已记录 DTO 的重放入口
run_workflow.rs 只保留输入输出类型、三步编排与检查点恢复判定
把 DRIFT_REASON 与两条漂移文案收敛到 write_back 模块,避免编排层和写回层各写一套
同步两份 UI 编辑器技术方案的模块布局与代码地图
2026-09-24 13:09:57 +08:00
k88936 6ef64c3bcf 将 design_doc 的切分模块提升为目录模块
把 steps/separate.rs 提升为 steps/separate/mod.rs,只保留纯 State 变换
把 design_doc/cut_images.rs 移到 steps/separate/cut_images.rs,与它服务的切分步骤同层
把 normalize_cut_image_path 从 steps/mod.rs 下沉到 steps/separate/mod.rs,紧邻两个使用点
run_workflow.rs 改从 steps::separate 统一导入切分入口与登记入口
同步两份 UI 编辑器技术方案的模块布局与代码地图
2026-09-24 13:05:44 +08:00
k88936 fbb161b33e 技术方案标注三个工具的落地顺序已完成 2026-09-23 21:51:52 +08:00
k88936 e8a33591c1 修正 run-workflow 的工具描述口径
- 明确本工具会把切分素材回填到 Image 组件、清组件状态、写 NeedReview 后写回并推进 revision
- 明确不做页面发现、多树合并与独立组件绑定,漂移时本轮作废需重新调用
2026-09-23 21:48:51 +08:00
k88936 7f4012697f run-workflow 的项目权限判据与工具命令映射对齐
- 内部 enforce_project_permission_policy 改用与并行账本一致的 asset.register,避免引入无法在项目策略里配置的私有命令 ID
2026-09-23 21:48:12 +08:00
k88936 784f6f0e32 收敛 design_doc 未使用接口并补齐切分 sidecar 收尾
- run-workflow 干净跑完后调用 finalize_separation 清理 sidecar,有回填问题时保留给编辑器恢复,与前端切分链路一致
- 检查点恢复校验轮次归属文档,漂移与写回冲突说明带上基线 revision
- WorkflowLog 删除仅供测试的 path 访问器,测试直接用 workflow_log_path
- mod.rs 不再导出仅内部使用的 next_ui_design_path,消除未使用告警
- 技术方案同步 sidecar 收尾口径
2026-09-23 21:44:45 +08:00
k88936 544f9ac040 重写 UI 设计文档桥接单测到当前入口
- 退役 ensureUiDesignResourceForPrototype / findLinkedUiDesignResource 的 6 条旧用例已随实现删除而无对应导出,测试文件整体失败
- 改为覆盖 createUiDesignDocFromImages:空图片列表拒绝且不调用命令,正常调用透传项目身份与设计图引用
2026-09-23 21:35:57 +08:00
k88936 e360d12ccb 同步 UI 设计文档工具化重写的文档口径
- 代码地图补 design_doc 各模块与三个 Agent 工具,并标注旧工具已退役
- project-overview 用三个新工具替换 ui.workflow.run 的旧口径
- App 实施计划追加 2026-09-23 节,记录工具拆分、检查点恢复与退役范围
- decision-log 新增本次决策、取舍与验证方式
- UI 工作流资源桥接旧方案标记 historical,从 docs/README 移入历史集合
2026-09-23 21:33:23 +08:00
k88936 b8d218f741 注册 UI 设计文档三个 Agent 工具
- 新增 agent/runtime_tools/ui_design_doc.rs:三个工具的参数解析、项目 ID 与 provider 身份注入、manifest 失效广播
- agent_native_tools 增加三个工具的描述与入参 schema,函数名归一同时处理 '.' 与 '-'
- Runtime 可执行工具目录、并行账本命令映射、design-foundation 自主白名单同步登记
- 新增工具描述可用性与入参校验单测
2026-09-23 21:30:50 +08:00
k88936 116d9d5940 新增 run-workflow 编排、切图素材登记与检查点工具函数
- 新增 design_doc/run_workflow.rs:recognize → separate → write-back 编排,按检查点行恢复,文档漂移追加 outdated 并报错
- 新增 design_doc/cut_images.rs:按切分路径登记图片并构造 SpriteAsset,按路径复用 manifest 条目、失败不回滚
- persistence 新增 load_ui_design_document_snapshot_at 并让 load_ui_design_state_at 复用它
- checkpoint 新增 checkpoint_timestamp 秒级时间戳
- 切分路径归一下沉到 steps/mod.rs,separate 步骤改用共享实现
- 技术方案同步 design_doc 模块布局与 into-js 复用 persistence 的说明
2026-09-23 21:08:52 +08:00
k88936 3c4c546ed5 新增自动切分结果落 State 的镜像步骤
- Node 新增 find_mut,深度优先可变定位节点,镜像前端 findUiNodeLocation
- 新增 design_doc/steps/separate.rs:素材合并去重与资源校验、按 bound_nodes 回填 target_graphic、统一写 problematic_nodes 的 NeedReview
- 覆盖重复素材、缺失素材、节点缺失、非 Image 组件、已绑定其他素材与问题文案回退的单测
2026-09-23 19:51:16 +08:00
k88936 b1a356aa0d 新增自动切分素材构造器与 design_doc 共用夹具
- SpriteAsset 新增 path 访问器与 from_registered_image,镜像前端 prepareSpriteAssetBatch 的空名称、无边框、像素比 1 口径
- 新增 design_doc/test_support.rs,集中 State、节点、素材的最小构造夹具
- recognize 单测改用共用夹具,删除本地重复的 image/state_with/root 构造
2026-09-23 19:41:01 +08:00
k88936 b34f1c9b3a 新增结构识别结果落 State 的镜像步骤
- design_doc/steps/recognize.rs:apply_recognition 整树替换,并按 DTO 顺序逐棵重推横向偏移
- 镜像 deriveTreeOffset / treeSize 与 UI_TREE_PADDING=48,先落位的树把后面的树推到右边
- 设计图缺失或像素比非法时按前端同口径报「界面图 X 缺少合法尺寸」
- 三个单测覆盖左右排布、整树替换与缺失设计图失败
2026-09-23 19:32:13 +08:00
k88936 f9a2cbe26e 新增工作流检查点日志模块
- design_doc/checkpoint.rs:run / recognize / separate / write-back / outdated 五种行
- 路径推导 ui/.<文档名>-workflow.jsonl,与文档同级且不进 manifest
- 追加时先截断崩溃留下的半行再一次性写入并 sync,避免半行夹在日志中间
- 扫描只保留最后一轮,写回或 outdated 视为轮次结束;坏行失败关闭
- 载荷一律按 serde_json::Value 存回,模块不认识业务 DTO 与 State 类型
- 六个单测覆盖路径推导、轮次遮蔽、两种结束行、半行丢弃与坏行失败
2026-09-23 19:24:07 +08:00
k88936 8ac8f2ff5d 补充 run-workflow 的轮次、漂移与写回幂等口径
- 检查点行补充 outdated 与起始 revision,明确 write-back / outdated 都是轮次结束行
- 追加前截断半行,避免崩溃留下的半行夹在日志中间
- 文档中途漂移时追加 outdated 并返回错误,由下一次调用显式开新一轮,不在同一次调用内自动重启
- 写回按「目标 State 与文档当前 State 相等」判幂等,并记录该判据成立的前提
- 切图资源失败不回滚,NeedReview 由 separation DTO 的 problematic_nodes 在编排阶段统一回写
2026-09-23 19:12:19 +08:00
k88936 648ead2150 新增 UI 设计文档创建入口 create_ui_design_doc_from_images
- 新增 ui_editor/design_doc/creation.rs:由一至四张设计图新建文档,未登记路径顺带登记成 ui-design 图片资源,文档内设计图身份取图片 assetId
- persistence.rs 把单图初始化泛化为 initialize_ui_design_state_with_images_at,支持多张设计图并拒绝重复
- 删除 resource_bridge.rs 与 create_ui_design_resource、ensure_ui_design_resource_for_prototype 两个命令,取消「原型 → 已存在文档」的幂等查找
- main.rs 注册 create_ui_design_doc_from_images 并移除两个旧命令注册项
- 前端 uiDesignResourceBridge 改为调用新命令,project-development 去掉 ui-workflow.completed 的自动打开与阶段跳转分支
- 代码地图同步 design_doc 模块与关键命令列表,测试改为覆盖新入口的编号避让
2026-09-23 18:14:15 +08:00
k88936 665788760e 新增 UI 设计文档工具的提示词目录模块
- 新增 prompts/runtime/texts/ui-design-doc.json,承载 from-images、run-workflow、into-js 三个工具的描述与参数文案
- manifest.json 的 textCatalogs 登记 uiDesignDoc 目录
2026-09-23 17:51:40 +08:00
k88936 02e29ba555 新增 UI 编辑器 Agent 工具化重写技术方案
- 新增技术方案:三个工具契约、两步工作流、JSONL 检查点、Rust 与提示词目录模块布局、落地顺序
- 代码地图同步退役 merge/binding/workflow.rs、关键命令列表与类型文件数量
- docs/README.md 收录该方案
2026-09-23 17:49:27 +08:00
k88936 3a35127b3b 记录 UI 设计文档与检查点日志术语及 ADR
- CONTEXT.md 新增 UI 设计文档、设计图、UI 工作流步骤、工作流检查点日志四个词条
- 新增 ADR 说明检查点采用追加式 JSONL 日志并记录两种被否方案
- docs/README.md 收录该 ADR
2026-09-23 17:47:00 +08:00
k88936 fb49b5fc5d 前端移除 UI 编辑器合并与绑定调用
- 删除 features/ui-editor/merge.ts 与 MergeDTO、BindingDTO、BindingChange 类型文件
- useUiEditorPage 去掉 mergeUi 操作、合并运行态、合并状态与投影字段
- isAiRunning 不再把合并运行态计入编辑器忙状态
2026-09-23 17:46:06 +08:00
k88936 31a1660e41 退役 UI 编辑器多树合并与组件绑定链路
- 删除 ui_editor/commands/merge.rs 与 binding.rs 两个实现及其 MergeDTO、BindingDTO
- commands/mod.rs 去掉两个模块声明、DTO 导出与 impl 再导出
- main.rs 去掉 merge_ui、bind_components 两个 Tauri 命令及注册项
2026-09-23 17:44:48 +08:00
k88936 006d278e9f 退役 Agent 侧 ui.workflow.run 工作流工具
- 删除 ui_editor/workflow.rs 与 agent/runtime_tools/ui_workflow.rs 两个工作流实现模块
- agent_native_tools 去掉 ui.workflow.run 的工具描述与 schema 分支
- prompts/runtime/texts/native-tools.json 删除 ui.workflow.run.description 文案
- runtime_tools/policy、tool_policy_snapshot、parallel_ledger、project_gates 移除该工具的白名单与门禁引用
- action_execution 去掉 ui.workflow.run 分发分支,action_audit 删除整块工作流审计与枚举项
- runtime_protocol 的 verification 与 acceptance_graph 去掉工作流校验项
- tool_plan_handoff/content_validation 去掉 ui-design pages 校验分支
- ui_editor/mod.rs 去掉 workflow 模块声明
2026-09-23 17:43:48 +08:00
k88936 26c6bdbbd5 同步文档:界面图参考语义建议退役
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
- UI 编辑器代码地图更新 resource/commands 职责、关键命令列表、两步工作流与类型文件数量

- UI 工作流资源桥接文档改为普通桥接从 structure-recognition 开始

- UI 编辑器工作流完成通知弹窗文档收敛为两个动作并删除分析参考图文案

- UI 编辑会话模块边界删除 suggestion seam 描述

- App 实施计划追加 2026-09-23 退役条目覆盖旧 slave_to 口径

- decision-log 记录退役决策、合并优先级恒 0 与无迁移后果及验证结果
2026-09-23 13:30:13 +08:00
k88936 81dc7b0cd6 前端移除分析参考图步骤与界面图元数据编辑
- model.ts 收敛为“识别界面结构 / 自动切分素材”两步并删除界面角色标签表

- 删除 suggestion 异步操作、结果通知分支、uiDesignSuggestions 与其 ts-rs 生成类型

- InputSidebar、InspectorSidebar、PreviewWorkspace 的界面图显示名改用 path basename,Inspector 只保留尺寸与资源 ID

- 删除因步骤退役而不可达的 ImportOverview,ToolNavigation 改为两格,删除“清空界面归属”提示

- requisites 删除角色/归属前置校验与参考图分析结果校验,importAdapter 不再写入界面图元数据

- 测试夹具去掉界面图元数据,删除界面角色与归属用例,历史用例改用 sprite 名称断言
2026-09-23 13:30:04 +08:00
k88936 e18a398cdf 退役界面图参考语义建议的 Rust 链路
- 删除 ui_editor/commands/ui_design_suggestion.rs 与 main.rs 的 suggest_ui_design_semantic 命令注册

- 收敛 UIDesignImage 为 path/pixel_size/pixels_per_unit,删除 UIDesignImageRole 与 UIDesignImageMetadata

- 结构识别改为每张界面图各自一棵树,删除 recognition_root_image_ids 与 slave_image_ids 及 ROOT/SLAVE 上下文标注

- 多树合并删除 slave_to 祖先链优先级,输入树优先级恒置 0 并留 TODO

- 持久化校验删除 slave_to 引用与环校验,html 片段注释不再携带界面图 name/description

- 页面级工作流构造页面设计图时不再写入界面图元数据
2026-09-23 13:29:39 +08:00
k88936 d8c19d9187 新增 UI 编辑器代码地图与模块职责文档
- 定义 `Rust` 权威层、`React` 会话层与视图层的职责边界
- 描述数据流、关键模块设计与核心命令
- 补充扩展指引与关联文档链接
- 更新 `docs/README.md` 引用路径
2026-09-23 11:46:31 +08:00
k88936 d5e06de33e 固化键盘菜单键不被右键手势拦截的口径
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m58s
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m57s
Project CI / Frontend tests (pull_request) Successful in 3m6s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m41s
Project CI / Native shell tests (pull_request) Successful in 6m13s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 9m32s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m50s
- 实测 Chromium 151 下键盘菜单键触发的是 button: -1,previewRightPanGesture 注释写明只认按钮 2 的原因
- previewRightPanGesture.test.ts 增加 button: -1 不拦截的判据用例
- previewRightPanDrag.test.tsx 增加键盘菜单键仍走原有节点菜单路径的集成用例
- decision-log 条目补上该实测结论与新增用例的验证口径

验证:npx vitest run previewRightPanDrag.test.tsx previewRightPanGesture.test.ts(13 passed)
2026-09-22 15:35:51 +08:00
k88936 0ab04d0f64 预览画布接入右键拖拽平移
- PreviewWorkspace 右键按下记录起手并捕获指针,越过共用阈值后按起点全量 delta 平移视口,抬起时由 resolvePreviewRightPanRelease 裁决平移还是节点菜单
- 捕获阶段拦截按钮 2 的 contextmenu,拦截窗口保留到下一次指针按下之前,兼容"按下触发"与"抬起后触发"两类平台时序
- 右键抬起用 [data-node-id] 加树容器新增的 data-tree-id 命中节点,沿用"右键即选中该节点"语义;未平移但抬起在预览外时不弹菜单,空白网格右键静默
- 视口接线共享 isPanning,让空格、中键、右键三个平移绑定共用 grabbing 光标;onPointerCancel 拆成不进菜单的独立处理
- 新增 tests/previewRightPanDrag.test.tsx:右键拖拽只平移不弹菜单、阈值内仍弹菜单、空白右键不改选择、预览外抬起不弹、非右键 contextmenu 仍即时、中键平移回归、右键平移后左键拖动仍可用

验证:npx vitest run 定向 7 个文件 59 passed、apps/ai-game-creator-shell npx tsc --noEmit、改动文件 eslint、npm run check:encoding、git diff --check
2026-09-22 15:32:19 +08:00
k88936 f525b6dc70 新增预览右键平移手势状态机
- 新增 previewRightPanGesture.ts:按下起手、按共用阈值判定是否进入平移、按起点全量 delta 复用 moveViewportFromPan 算视口
- 抬起裁决统一收口到 resolvePreviewRightPanRelease:平移过就只回视口,未平移且在预览内才按节点菜单处理
- shouldInterceptRightContextMenu 记录"按钮 2 且处于右键按下序列"的拦截窗口,兼容按下触发与抬起后触发的两类平台时序
- 新增 tests/previewRightPanGesture.test.ts 覆盖阈值边界、越阈值后不回弹、全量 delta、预览外抬起与拦截判据

验证:npx vitest run previewRightPanGesture.test.ts(5 passed)
2026-09-22 15:27:30 +08:00
k88936 96e7ba7808 预览拖动阈值抽成公共常量模块
- 新增 previewDragThreshold.ts,导出 DRAG_THRESHOLD_SCREEN_PX 与屏幕坐标阈值判定 passedDragThresholdScreen
- useNodeTransformInteraction 删除本地阈值常量,改为复用该模块,左键拖动/缩放行为不变

验证:npx vitest run useNodeTransformInteraction.test.tsx previewWorkspaceZoom.test.tsx(18 passed)
2026-09-22 15:26:11 +08:00
k88936 65e06c4808 记录预览画布右键拖拽平移的交互约定
- decision-log 新增 2026-09-22 条目:右键拖拽平移的判定链、菜单改到抬起弹出、按下即拦截 contextmenu 的实测依据与代价
- 【UI编辑器】拖动变换提交边界补「视口平移不写 State、不进历史」以及右键平移与节点菜单按同一拖动阈值互斥的约定
- 同一文档的实现边界补拖动阈值落点 previewDragThreshold.ts 与右键裁决落点 previewRightPanGesture.ts
2026-09-22 15:25:32 +08:00
k88936 4cd5fb8d72 Merge remote-tracking branch 'origin/master' into feat/ui-editor-v3
# Conflicts:
#	apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationQueue.ts
#	apps/ai-game-creator-shell/src/features/resource-canvas/useResourceCanvasGenerationPlaceholders.ts
#	apps/ai-game-creator-shell/tests/resourceCanvasGenerationFloatingPanel.test.tsx
#	docs/project-memory/shared-memory/decision-log.md
2026-09-22 14:08:41 +08:00
k88936 47c07f9d12 迁移节点变换交互测试接口
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 4m8s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 4m25s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m24s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 5m13s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m34s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m39s
Project CI / Frontend tests (pull_request) Successful in 5m56s
Project CI / AI game creator shell web tests (pull_request) Successful in 8m34s
Project CI / Repository checks (pull_request) Successful in 15m18s
Project CI / Native shell tests (pull_request) Successful in 22m28s
Project CI / Backend tests (pull_request) Successful in 27m38s
将旧单树测试夹具改为 trees 与 logicalSizes 参数

为拖拽和缩放入口补充所属 treeId 并更新树移除语义
2026-09-18 22:33:21 +08:00
k88936 0904184412 校正预览上下文菜单测试
覆盖上下文菜单回调的可选树标识参数

区分编辑器树目标与最终预览的无树标识回调
2026-09-18 22:18:58 +08:00
k88936 aaa2eb482a 更新最终预览框线测试语义
无框线场景不再传入选中节点以匹配选中态高亮规则

保留显式 showFrame 时的选中框与控制点断言
2026-09-18 22:08:53 +08:00
k88936 1679e72deb 修正删除快捷键页面测试
从仍挂载的窗口目标触发全局删除快捷键

按全部同名节点消失断言删除结果
2026-09-18 22:05:16 +08:00
k88936 4844543061 补齐检查提示动画测试名称
让动画 mock 匹配仅重启提示动画的现役筛选逻辑

保留非提示动画不被重启的生产行为
2026-09-18 22:00:58 +08:00
k88936 99132c9fa7 补齐 UI 状态持久化测试偏移字段
让当前 schema 夹具继续验证原有业务约束
2026-09-18 21:41:55 +08:00
k88936 944d2aa3db 更新 UI 编辑器返回交互测试
按现役浮动关闭按钮的可访问名称查询
2026-09-18 21:14:57 +08:00
k88936 7c16a35fda 兼容缺少偏移量的预览运行数据
校验 offset 前安全处理旧状态和测试夹具
2026-09-18 20:14:24 +08:00
k88936 e4369cd82e Merge remote-tracking branch 'origin/master' into feat/ui-editor-v3
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 5m0s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m54s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 7m15s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 6m32s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 6m56s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m21s
Project CI / Frontend tests (pull_request) Failing after 4m23s
Project CI / Repository checks (pull_request) Failing after 5m22s
Project CI / Native shell tests (pull_request) Successful in 9m10s
Project CI / Backend tests (pull_request) Successful in 9m41s
Project CI / AI game creator shell web tests (pull_request) Failing after 5m32s
# Conflicts:
#	apps/ai-game-creator-shell/tests/uiEditorPage.test.ts
#	docs/project-memory/shared-memory/decision-log.md
2026-09-18 18:44:11 +08:00
k88936 54b9bd4354 同步节点偏移量 TypeScript 绑定
更新 ts-rs 生成的 NodeOffset 类型文件
2026-09-18 17:30:53 +08:00
k88936 365e530b5f 修正测试偏移类型导入
从公开 offset 模块导入 NodeOffset
2026-09-18 17:23:06 +08:00
k88936 d46cdfb282 抽取保存结果弹窗操作底栏
统一关闭与重试按钮的布局和样式
2026-09-18 17:17:45 +08:00
k88936 2142027f85 简化保存结果弹窗内容分支
用显式早返回替换嵌套三元渲染
2026-09-18 17:15:50 +08:00
k88936 8fdf39f37e 清理预览初始适配无效依赖
移除仅通过 ref 读取的画布尺寸依赖
2026-09-18 17:14:16 +08:00
k88936 70c65c37d5 保证拖拽释放提交最新偏移
从指针释放坐标直接计算树位置
2026-09-18 17:12:30 +08:00
k88936 e72637789c 校验预览树偏移坐标
仅将有限偏移量纳入画布布局
2026-09-18 17:11:23 +08:00
k88936 0a102ea1c3 合并预览节点类型导入
复用单一 Node 类型导入声明
2026-09-18 17:10:04 +08:00
k88936 1ab7fa9991 校验预览画布像素比例
避免无效 pixels_per_unit 产生无限尺寸
2026-09-18 17:09:17 +08:00
k88936 6d02a0c29f 移除无读取的代码生成错误状态
删除失效的 generateError 状态及写入路径
2026-09-18 17:04:10 +08:00
k88936 a6c3db60fb 收窄编辑器操作生命周期接口
移除未被调用的 setRunning 与 reset 暴露
2026-09-18 16:57:42 +08:00
k88936 c921ae3b16 简化编辑器概览分支渲染
用显式条件替换嵌套三元表达式
2026-09-18 16:52:37 +08:00
k88936 31c084fce6 保留保存失败的底层错误信息
将实际异常传递到保存状态和重试提示
2026-09-18 16:48:53 +08:00
k88936 1e7a3f17ff 避免返回确认与失败弹窗重叠
返回路径保存失败时沿用确认弹窗错误状态
2026-09-18 16:45:43 +08:00
k88936 3ce2977e32 固定节点偏移量 TypeScript 元组导出
为二维数组字段补充 ts-rs 元组覆盖
2026-09-18 16:43:26 +08:00
k88936 f4e9414d63 限定状态提示动画重启动范围
仅重启动 UI 编辑器关注动画而不影响过渡效果
2026-09-18 16:41:12 +08:00
k88936 2d8b51885a 优化切分概览卡片布局
将窄侧栏概览调整为两列网格
2026-09-18 16:39:39 +08:00
k88936 91f534ed66 优化识别概览卡片布局
将窄侧栏概览调整为两列网格
2026-09-18 16:38:25 +08:00
k88936 f455d3edb2 清理未使用的界面图校验码
移除不会被发出的 missing-image 类型
2026-09-18 16:37:48 +08:00
k88936 bec225436f 简化状态节点轮询索引逻辑
用显式分支替换嵌套三元表达式
2026-09-18 16:35:25 +08:00
k88936 ce309a3b28 忽略未定义的节点元数据补丁
保留部分更新对未提供字段的原有语义
2026-09-18 16:33:37 +08:00
k88936 abd95a6231 消除复制路径未处理拒绝
复制失败仅保留界面错误状态
2026-09-18 16:31:04 +08:00
k88936 08c3b89b67 修复跨树节点选择同步界面图
选择节点时同步激活所属设计图
2026-09-18 16:28:49 +08:00
k88936 3371337346 修复树节点回调未使用参数
为跨树选择回调参数添加下划线前缀
2026-09-18 16:25:54 +08:00
k88936 899a591cda 区分语义建议工具调用缺失错误
恢复缺失工具调用的独立日志阶段和提示
2026-09-18 16:23:27 +08:00
k88936 0988c4907a 区分识别工具调用缺失错误
恢复识别工具缺失的独立日志阶段和提示
2026-09-18 16:20:49 +08:00
k88936 d370648867 区分合并工具调用缺失错误
恢复缺失工具调用的独立日志阶段和提示
2026-09-18 16:13:58 +08:00
k88936 4f4fb0873c 修复切分命令测试节点构造
补齐 NodeOffset 默认值以恢复 Rust 测试构建
2026-09-18 16:09:03 +08:00
k88936 6b8a106414 修复合并命令测试节点构造
补齐 NodeOffset 默认值以恢复 Rust 测试构建
2026-09-18 16:08:53 +08:00
k88936 55a8513de4 更新UI编辑器深模块架构文档
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 4m16s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 4m23s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 4m24s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 4m26s
Project CI / Backend tests (pull_request) Failing after 13s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m52s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m28s
Project CI / Frontend tests (pull_request) Failing after 3m30s
Project CI / AI game creator shell web tests (pull_request) Failing after 4m33s
Project CI / Native shell tests (pull_request) Successful in 9m17s
记录状态迁移、几何、不变量与操作生命周期 seam

同步结构化 LLM 适配器边界与验证结果
2026-09-18 10:24:03 +08:00
k88936 d986dbaeb0 收敛UI编辑器结构化LLM动作适配器
统一必需 tool-call 定位与有界 JSON 参数解析

让语义建议、结构识别、组件绑定和多树合并复用稳定机械

保留各操作的 prompt、schema、领域校验与 materializer
2026-09-18 10:23:35 +08:00
k88936 16c6b9198e 深化UI编辑器前端状态与几何模块
新增 React-free 语义状态迁移 seam

收敛 State 级节点几何与保存前不变量校验

统一四类异步编辑器操作生命周期

补充状态迁移和不变量测试
2026-09-18 10:23:04 +08:00
k88936 4df4708ba1 Merge remote-tracking branch 'origin/master' into feat/ui-editor-v3
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 3m0s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 3m4s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 3m7s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 3m9s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m41s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m32s
Project CI / Frontend tests (pull_request) Failing after 4m47s
Project CI / Repository checks (pull_request) Failing after 4m39s
Project CI / Native shell tests (pull_request) Successful in 7m14s
Project CI / Backend tests (pull_request) Successful in 8m12s
Project CI / AI game creator shell web tests (pull_request) Failing after 2m54s
# Conflicts:
#	docs/project-memory/shared-memory/pitfalls.md
2026-09-16 14:57:26 +08:00
k88936 1e992bcdf8 扩大未知 JavaScript 异常上报原则
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
明确所有未解决和非契约化 JS 异常必须继续抛出

说明用户 fallback 不得替代全局 error report 链路
2026-09-14 19:47:08 +08:00
k88936 8368aa262c 补充剪贴板异常上报约定
记录 UI fallback 后仍需抛出原始原生错误

明确未知异常必须进入 error report 链路
2026-09-14 19:43:08 +08:00
k88936 e9ddbf16da 重构预览网格步长计算
提取预览网格自适应步长到独立模块

补充缩放网格计算与背景样式测试

同步预览画布缩放交互文档
2026-09-14 19:40:13 +08:00
k88936 22ac1f4c0b 修复 UI 编辑器剪贴板写入权限
为主窗口补充 clipboard-manager 写文本权限

保留手动复制提示并继续抛出原始 Tauri 剪贴板错误
2026-09-14 19:39:34 +08:00
k88936 9910a0eec0 优化 UI 编辑器复制反馈布局
将复制失败提示与操作按钮分离排列
2026-09-14 19:06:15 +08:00
k88936 70981b9ca9 完善 UI 编辑器结果弹窗覆盖
增加普通保存成功与保存后生成失败的场景断言

验证路径弹窗和失败重试按钮的可见行为
2026-09-14 19:01:44 +08:00
k88936 8a0d5600b3 拆分 UI 编辑器路径复制按钮
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 2m27s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 2m23s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 2m5s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 2m8s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m57s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m15s
Project CI / Frontend tests (pull_request) Failing after 3m22s
Project CI / Repository checks (pull_request) Successful in 3m38s
Project CI / Native shell tests (pull_request) Successful in 6m42s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m12s
Project CI / Backend tests (pull_request) Successful in 8m24s
将剪贴板状态与写入逻辑提取为独立小组件

保持保存结果弹窗只负责结果布局与动作编排
2026-09-14 18:59:51 +08:00
k88936 184d88dbb8 修复多树预览受左侧选中图片限制
移除 activeImageId 对预览拖动和缩放手势的门控

按树 wrapper 提供几何上下文并允许任意树操作

保留 activeImageId 仅用于左侧图片与图片级检查
2026-09-14 18:59:51 +08:00
k88936 791794c0a6 记录 UI 编辑器保存结果弹窗约定
补充保存与代码生成结果弹窗及相对路径复制行为

记录复制失败、重试和保存并返回边界
2026-09-14 18:59:51 +08:00
k88936 9cfb47d945 补充 UI 编辑器保存结果测试
覆盖保存和生成成功弹窗与项目相对路径复制

覆盖保存失败、冲突和剪贴板失败反馈
2026-09-14 18:59:51 +08:00
k88936 27859d3e19 接入 UI 编辑器保存结果弹窗
让普通保存和保存并生成流程返回结构化结果

将成功与失败反馈接入独立弹窗并保留保存并返回直返行为
2026-09-14 18:59:51 +08:00
k88936 43f3780a38 新增 UI 编辑器保存结果弹窗
新增保存成功、代码生成成功和失败状态弹窗

提供项目相对路径复制按钮及复制失败提示
2026-09-14 18:59:51 +08:00
k88936 a1f9149c27 UI编辑器移除预览模式残留类型
完成三开关替代 render mode 的类型清理
2026-09-14 18:59:51 +08:00
k88936 f0bba18841 UI编辑器收口多树上下文菜单目标
预览菜单携带所属树 ID

避免非当前图片树的结构操作写错目标
2026-09-14 18:59:51 +08:00
k88936 f679cfa179 UI编辑器完善多树创建与文档契约
统一识别和合并结果的 createTree 流程

按选中节点所属树驱动 Inspector 编辑

补充多树预览与树级偏移权威文档
2026-09-14 18:59:50 +08:00
k88936 f68ffcfecd UI编辑器补充合并树偏移构造
为合并产出的节点补齐 NodeOffset
2026-09-14 18:59:50 +08:00
k88936 ec94a4fe00 UI编辑器重构多树预览与独立开关
预览画布同时渲染全部界面树并支持树级拖动

拆分显示框线、原图和组件开关

预览节点选择与左侧当前图片解耦
2026-09-14 18:59:50 +08:00
k88936 46f8592375 UI编辑器补充树级偏移模型
新增 Node offset min/max 数据结构

统一新树默认横向布局入口

增加树级偏移状态更新操作与实施计划
2026-09-14 18:59:50 +08:00
k88936 cdf2882302 补齐自动切分概览字体属性
向 SeparationOverview 传入 fonts 数据

修复 UI 编辑器 TS2741 类型错误
2026-09-14 18:59:50 +08:00
k88936 d156113e71 修复 UI 编辑器待办循环高亮动画
修复受控树选中回调清除 Inspector 高亮的问题

循环游标同时使用界面树与节点 ID

补充多节点循环和动画重启回归测试
2026-09-14 18:59:50 +08:00
k88936 a8f2ac17be 注释掉 InspectorSidebar 中的 LayoutEditor 组件以暂时禁用布局编辑功能 2026-09-14 18:59:50 +08:00
k88936 43ac9a5761 补充 UI 编辑器交互回归测试
从实际节点事件目标验证删除快捷键

验证 portal 右键菜单启用后会执行操作并关闭
2026-09-14 18:59:50 +08:00
k88936 e992e0b35c 压缩 UI 编辑器概览布局
将识别概览指标改为四列横向排列

将素材切分概览改为四列网格以减少纵向高度
2026-09-14 18:59:50 +08:00
k88936 e389b54a3a 修复 UI 预览画布交互
为无限视口添加跟随平移和缩放的点阵背景

恢复画布快捷缩放与初始适配比例控制

保留节点平移选择并屏蔽浏览器原生拖拽选择
2026-09-14 18:59:50 +08:00
k88936 84c780a6f8 重构 UI 编辑器全屏外壳
通过 portal 将资源编辑器挂载到 body 并覆盖标题栏以下区域

移除外层 FocusTrap,保留内部确认对话框的模态行为

统一左右浮动面板的顶部与底部留白
2026-09-14 18:59:50 +08:00
k88936 03f900fbe5 布局重构:
- 预览改为全区域背景层。
  - 左右面板、顶部流程条改为覆盖层,可遮挡预览。
  - 左右面板开合状态仅保存在当前 React 会话。
  - 增加左右面板“收起/展开”入口。
  - 编辑器关闭入口改为单独的 ×,继续通过 onBack 注入关闭逻辑。
  - 保留全局顶部栏与现有快捷键、业务状态、保存流程。
  - 底层页面仍由现有父级路由控制,不新增 URL 状态。
2026-09-14 18:59:50 +08:00
150 changed files with 8698 additions and 9373 deletions
+17
View File
@@ -48,6 +48,23 @@ _Avoid_: 无来源的静态素材、只显示在 UI 但不落工程资源记录
一组同类素材的统一批量生成方式,采用批量规划、sheet 生图、后端切图、透明化、OSS 持久化和局部重生成的通用流水线。
_Avoid_: 为每个玩法单独发明素材流水线、把系列素材建模成任一玩法专属 DTO
**UI 设计文档**:
AGC 本地项目里 `kind=ui-design-doc``mediaType=application/json` 的界面编辑载体,保存设计图集合、UI 树、组件绑定和 State revision;一份文档可同时承载多张设计图与各自的结构树,不强制收敛成一棵树。
_Avoid_: 把 UI 设计图当成设计文档、给设计文档再套一层「页面」概念
**设计图**:
UI 设计文档里的一张参考界面图,以它在 manifest 中的资产 ID 作为文档内身份,附带像素尺寸与像素比;文档内的每棵树都必须引用文档里已存在的设计图。
_Avoid_: 用文件名当设计图身份、设计图与设计文档同一概念
**UI 工作流步骤**:
对一份 UI 设计文档执行的一次受控处理阶段,当前只有「结构识别」与「素材切分」两步;步骤产物只有在写入文档并保存后才算完成。
_Avoid_: 把单次工具调用当成步骤、把中间产物当成步骤完成
**工作流检查点日志**:
一份 UI 设计文档旁按行追加的恢复用日志,每完成一个工作流步骤追加一行;某步是否有对应行即代表该步是否完成,恢复时从第一个缺失的行继续。
_Avoid_: 每步一个 sidecar 状态机、把切分专用的 SeparationState 泛化成通用检查点
## Language
### Puzzle Clear
@@ -98,13 +98,10 @@ const appInvokeSources = readSourceFiles(
new URL('../src/', import.meta.url),
new Set(['.ts', '.tsx']),
);
// 生产接线在 desktop,受测的启动诊断实现仍在 main;两者共同构成启动检查范围。
const tauriHandlerSource = [
const tauriHandlerSource = fs.readFileSync(
new URL('../src-tauri/src/main.rs', import.meta.url),
new URL('../src-tauri/src/desktop.rs', import.meta.url),
]
.map((source) => fs.readFileSync(source, 'utf8'))
.join('\n');
'utf8',
);
const tauriRustSource = readSourceTree(
new URL('../src-tauri/src/', import.meta.url),
'.rs',
@@ -160,11 +157,8 @@ const allowedUncalledTauriCommands = [
'set_active_game_creator_agent_session',
'start_game_creator_agent_goal',
'start_game_creator_supervisor_runtime_task',
// 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',
// 图片类生成的同步变体:GUI 已改为 `start_local_project_asset_generation` + 项目内任务账本
// (提交即返回、后台生成)。这条命令**没有生产调用方**,只有 Rust 集成测试
// `src/tests/project.rs`)与 `commands.rs` 单测在调;待后续批次删除,或改为转调
@@ -1,11 +1,12 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main",
"description": "AI 游戏创作主窗口允许读系统剪贴板图片,用于粘贴素材附件;允许弹出原生打开/保存对话框用于素材上传与导出。",
"description": "AI 游戏创作主窗口允许读系统剪贴板,用于粘贴素材附件和复制生成文件路径;允许弹出原生打开/保存对话框用于素材上传与导出。",
"windows": ["client"],
"permissions": [
"clipboard-manager:allow-read-image",
"clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-text",
"core:image:allow-rgba",
"core:image:allow-size",
"core:resources:allow-close",
@@ -7,6 +7,7 @@
"execution": "texts/execution.json",
"media": "texts/media.json",
"nativeTools": "texts/native-tools.json",
"uiDesignDoc": "texts/ui-design-doc.json",
"recovery": "texts/recovery.json",
"interaction": "texts/interaction.json",
"goalContext": "texts/goal-context.json",
@@ -37,7 +37,6 @@
"preview.validate.description": "用真实浏览器验证桌面和移动预览并保存证据。",
"image.inspect.description": "让视觉模型检查一至两张项目内图片。",
"canvas.asset_generate.description": "通过已配置的 External Editor API 按项目需求生成图片或图集并登记到画布、素材库和项目 assets;可使用已登记资源作为参考。assetKind=icon-spritesheet 的 prompt 去除首尾空白后须为 1 到 200 个 Unicode 字符,保留内部换行并作为单条 iconDescriptions 原样提交,超限拒绝,不截断、不拆条,客户端不追加生图指令。assetKind=icon-spritesheet 时 sliceMode 必填且没有默认值:需求要求等分网格、固定槽位或指定行列数时用 grid 并提供来自需求本身的 gridX/gridY;自由排布、数量不定或只要求一张图集时用 connected-components,可用 sliceCount 约束素材张数;其它 assetKind 不得携带 sliceMode/gridX/gridY。",
"ui.workflow.run.description": "先用 discover 从受控 game/ui-pages.json 或页面声明标记自动发现全部功能页面,再把已登记 ui-design 与每个页面的设计图桥接成独立 UI JSON State;可同时载入已登记图片、图标和项目字体,执行 Provider 结构识别、多树合并与分批组件绑定、回读阶段,并且只有所有页面已绑定且已应用到 game/ 后才允许 finalize。项目根目录由 Runtime 注入,模型不得传入宿主路径。",
"cocos.editor.execute.description": "在当前项目对应的已打开 Cocos Creator 编辑器中执行一段有界代码;仅提交 code,客户端负责绑定项目与编辑器进程。",
"unity.editor.execute.description": "在当前 Unity 项目已打开的编辑器中执行 C#。仅提交 code;结果待核对时禁止自动重发。",
"godot.editor.execute.description": "在当前 Godot 项目已打开的编辑器中执行支持 return/await 的 GDScript 函数体。仅提交 code;结果待核对时禁止自动重发。",
@@ -0,0 +1,8 @@
{
"from_images.description": "用一至四张设计图新建一份 UI 设计文档:输入给已登记资源的 assetId,或给项目内相对路径由本工具顺带登记;随后按 ui/UI 设计 N.json 取号创建文档、把设计图写进文档并登记为 ui-design-doc 资源,返回新文档的 assetId 与 relativePath。每次调用都新建文档,不复用既有文档。",
"from_images.parameters.images": "一至四张设计图。每张给 assets 里已登记图片资源的 assetId,或给项目内相对路径(相对路径会先登记成图片资源);文档内的设计图身份就是该图片资源的 assetId。",
"run_workflow.description": "对一份 UI 设计文档在 Rust 内依次执行结构识别与自动切分素材:把切分素材回填到 Image 组件的 target_graphic、清掉已处理节点的组件状态、给达到返工上限的问题节点写 NeedReview,最后写回文档并推进 revision。每完成一步追加一行检查点,崩溃后从第一个缺失的步骤继续;不做页面发现,也不做多树合并与独立组件绑定。文档在轮次中途被改动时本轮作废并报错,必须重新调用本工具开新一轮。",
"run_workflow.parameters.designDocAssetId": "目标 UI 设计文档在 assets 里的 assetId;文档内必须已有设计图。",
"into_js.description": "把一份 UI 设计文档的当前 revision 渲染成 ui/generated-<stem>-<digest>.js,返回相对路径与导出树。只读文档内容,不修改文档、不推进 revision。",
"into_js.parameters.designDocAssetId": "目标 UI 设计文档在 assets 里的 assetId。"
}
@@ -45,19 +45,16 @@ mod runtime_protocol;
mod runtime_state;
mod runtime_tools;
mod skill_pack;
pub(crate) use codex_app_server::direct_game_creator_codex_chat_at;
use codex_app_server::*;
#[cfg(not(test))]
pub(crate) use codex_app_server::{
cancel_direct_codex_turn_at, direct_game_creator_home_codex_chat, direct_thread_id_for_project,
DirectTurnCancelView,
cancel_direct_codex_turn_at, direct_game_creator_codex_chat_at,
direct_game_creator_home_codex_chat, direct_thread_id_for_project, DirectTurnCancelView,
};
use codex_cli::*;
pub(crate) use codex_cli::{
game_creator_codex_cli_executable_path, game_creator_codex_cli_version_identity,
};
pub(crate) use codex_provider_proxy::*;
#[cfg(not(test))]
pub(crate) use design_runtime::*;
pub(crate) use direct_codex_attachments::*;
pub(crate) use direct_codex_user_item::*;
@@ -2480,7 +2480,42 @@ impl CodexAppServerConnection {
Ok(connection)
}
#[cfg(all(test, unix))]
#[cfg(test)]
async fn spawn(
llm: &GameCreatorLlmConfig,
credential: &CodexAppServerCredential,
) -> Result<Self, platform_llm::LlmError> {
let executable = game_creator_codex_cli_executable_path()
.map_err(platform_llm::LlmError::InvalidConfig)?;
Self::spawn_with_executable_and_credential(llm, credential, executable.as_os_str()).await
}
#[cfg(test)]
async fn spawn_with_executable(
llm: &GameCreatorLlmConfig,
executable: &std::ffi::OsStr,
) -> Result<Self, platform_llm::LlmError> {
let credential = resolve_game_creator_codex_app_server_credential(llm)?;
Self::spawn_with_executable_and_credential(llm, &credential, executable).await
}
#[cfg(test)]
async fn spawn_with_executable_and_credential(
llm: &GameCreatorLlmConfig,
credential: &CodexAppServerCredential,
executable: &std::ffi::OsStr,
) -> Result<Self, platform_llm::LlmError> {
Self::spawn_with_executable_and_credential_at_workspace(
llm,
credential,
executable,
None,
CodexAppServerWorkspaceMode::ToolHost,
)
.await
}
#[cfg(test)]
async fn spawn_with_executable_and_credential_at_workspace(
llm: &GameCreatorLlmConfig,
credential: &CodexAppServerCredential,
@@ -7060,8 +7095,6 @@ while IFS= read -r line; do :; done
.map(|argument| argument.to_string_lossy().into_owned())
.collect::<Vec<_>>()
.join("\n");
assert!(home_arguments.contains("--disable\nshell_tool"));
assert!(home_arguments.contains("--disable\nunified_exec"));
}
#[cfg(windows)]
@@ -1688,6 +1688,7 @@ pub(crate) fn read_design_workspace_file(
#[cfg(test)]
mod fake_provider {
use super::*;
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
@@ -8,7 +8,6 @@ use std::sync::{Mutex, OnceLock};
use std::time::{SystemTime, UNIX_EPOCH};
mod user_input;
#[cfg(not(test))]
pub(crate) use user_input::chat_with_game_creator_direct_codex;
#[cfg(test)]
pub(crate) use user_input::normalize_direct_client_turn_id;
@@ -49,6 +49,7 @@ pub(in crate::agent) use external_generation_state::{
};
#[cfg(test)]
pub(crate) use external_generation_state::{
platform_art_generation_external_configuration_fingerprint,
platform_art_generation_legacy_external_configuration_fingerprint,
setup_platform_art_generation_runtime_accepted_for_recovery_test,
write_platform_art_generation_runtime_accepted_for_test,
@@ -677,161 +677,6 @@ fn agent_runtime_action_receipt_safe_detail_with_owner(
"revisionAdvanceCount": value.get("revisionAdvanceCount").and_then(serde_json::Value::as_u64).unwrap_or(0),
})).ok();
}
if observation.tool == "ui.workflow.run" {
let value = serde_json::from_str::<serde_json::Value>(
observation.detail.as_deref().unwrap_or_default(),
)
.ok()?;
let operation = value.get("operation")?.as_str()?;
let project_id = value.get("projectId")?.as_str()?;
let source_asset_id = value.get("sourceAssetId")?.as_str()?;
let completed = value.get("completed")?.as_bool()?;
let revision_advance_count = value.get("revisionAdvanceCount")?.as_u64()?;
if project_id.is_empty()
|| source_asset_id.is_empty()
|| !matches!(
operation,
"discover" | "prepare" | "recognize" | "status" | "finalize"
)
|| project_id.chars().any(char::is_control)
|| source_asset_id.chars().any(char::is_control)
{
return None;
}
if operation == "discover" {
let discovered_pages = value.get("discoveredPages")?.as_array()?;
if discovered_pages.is_empty() || discovered_pages.len() > 32 {
return None;
}
let safe_pages = discovered_pages
.iter()
.map(|page| {
let page_id = page.get("pageId")?.as_str()?;
let title = page.get("title")?.as_str()?;
let description = page.get("description")?.as_str()?;
let application_path = page.get("applicationPath")?.as_str()?;
let required_design_asset_path =
page.get("requiredDesignAssetPath")?.as_str()?;
let discovered_from = page.get("discoveredFrom")?.as_str()?;
if page_id.is_empty()
|| title.is_empty()
|| description.chars().any(char::is_control)
|| application_path.is_empty()
|| !application_path.starts_with("game/")
|| required_design_asset_path.is_empty()
|| discovered_from.is_empty()
{
return None;
}
Some(serde_json::json!({
"pageId": agent_runtime_action_receipt_safe_text(root, page_id, 80, None)?,
"title": agent_runtime_action_receipt_safe_text(root, title, 120, None)?,
"description": agent_runtime_action_receipt_safe_text(root, description, 400, None)?,
"applicationPath": agent_runtime_action_receipt_safe_text(root, application_path, 240, None)?,
"requiredDesignAssetPath": agent_runtime_action_receipt_safe_text(root, required_design_asset_path, 240, None)?,
"discoveredFrom": agent_runtime_action_receipt_safe_text(root, discovered_from, 240, None)?,
}))
})
.collect::<Option<Vec<_>>>()?;
return serde_json::to_string(&serde_json::json!({
"operation": operation,
"projectId": agent_runtime_action_receipt_safe_text(root, project_id, 160, None)?,
"sourceAssetId": agent_runtime_action_receipt_safe_text(root, source_asset_id, 160, None)?,
"completed": completed,
"revisionAdvanceCount": revision_advance_count,
"pages": [],
"discoveredPages": safe_pages,
"finalStageRoute": null,
}))
.ok();
}
let pages = value.get("pages")?.as_array()?;
if pages.is_empty() || pages.len() > 32 {
return None;
}
let mut safe_pages = Vec::with_capacity(pages.len());
for page in pages {
let page_id = page.get("pageId")?.as_str()?;
let title = page.get("title")?.as_str()?;
let design_asset_id = page.get("designAssetId")?.as_str()?;
let ui_asset_id = page.get("uiAssetId")?.as_str()?;
let revision = page.get("uiStateRevision")?.as_u64()?;
let stage = page.get("stage")?.as_str()?;
let marker = page.get("applicationMarker")?.as_str()?;
let blockers = page.get("blockers")?.as_array()?;
if page_id.is_empty()
|| title.is_empty()
|| design_asset_id.is_empty()
|| ui_asset_id.is_empty()
|| revision > 9_007_199_254_740_991
|| marker.is_empty()
|| !matches!(
stage,
"reference-ready"
| "structure-ready"
| "binding-ready"
| "application-ready"
| "completed"
)
|| blockers.len() > 32
{
return None;
}
let safe_blockers = blockers
.iter()
.map(|blocker| {
let blocker = blocker.as_str()?;
agent_runtime_action_receipt_safe_text(root, blocker, 240, None)
.map(serde_json::Value::String)
})
.collect::<Option<Vec<_>>>()?;
safe_pages.push(serde_json::json!({
"pageId": agent_runtime_action_receipt_safe_text(root, page_id, 80, None)?,
"title": agent_runtime_action_receipt_safe_text(root, title, 120, None)?,
"designAssetId": agent_runtime_action_receipt_safe_text(root, design_asset_id, 160, None)?,
"uiAssetId": agent_runtime_action_receipt_safe_text(root, ui_asset_id, 160, None)?,
"uiStateRevision": revision,
"stage": stage,
"blockers": safe_blockers,
"applicationMarker": agent_runtime_action_receipt_safe_text(root, marker, 240, None)?,
}));
}
let final_stage_route = if completed {
let route = value.get("finalStageRoute")?;
let resource_id = route.get("resourceId")?.as_str()?;
let initial_step = route.get("initialStep")?.as_str()?;
let render_mode = route.get("renderMode")?.as_str()?;
if resource_id.is_empty()
|| initial_step != "asset-separation"
|| render_mode != "final-preview"
{
return None;
}
Some(serde_json::json!({
"resourceId": agent_runtime_action_receipt_safe_text(root, resource_id, 160, None)?,
"initialStep": initial_step,
"renderMode": render_mode,
}))
} else {
if !value
.get("finalStageRoute")
.is_some_and(serde_json::Value::is_null)
{
return None;
}
None
};
return serde_json::to_string(&serde_json::json!({
"operation": operation,
"projectId": agent_runtime_action_receipt_safe_text(root, project_id, 160, None)?,
"sourceAssetId": agent_runtime_action_receipt_safe_text(root, source_asset_id, 160, None)?,
"completed": completed,
"revisionAdvanceCount": revision_advance_count,
"pages": safe_pages,
"finalStageRoute": final_stage_route,
}))
.ok();
}
if observation.tool != "project.patchset" {
return None;
}
@@ -1210,7 +1055,6 @@ pub(in crate::agent) fn agent_runtime_public_action_input_summary(
| "preview.validate"
| "image.inspect"
| "canvas.asset_generate"
| "ui.workflow.run"
| "agent.message"
| "agent.delegate"
| "agent.schedule_ready"
@@ -153,6 +153,14 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_
observe_agent_runtime_account_asset_library(root, &action.input).await
}
"canvas.asset_import" => observe_agent_runtime_asset_import(root, &action.input).await,
"ui-design-doc.from-images" => {
observe_agent_runtime_ui_design_doc_from_images(root, &action.input)
}
"ui-design-doc.run-workflow" => {
observe_agent_runtime_ui_design_doc_run_workflow(root, agent_id, run_id, &action.input)
.await
}
"ui-design-doc.into-js" => observe_agent_runtime_ui_design_doc_into_js(root, &action.input),
"project.index" => observe_agent_runtime_project_snapshot_with_lock(
root,
agent_id,
@@ -436,9 +444,6 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_
)
.await
}
"ui.workflow.run" => {
observe_agent_runtime_ui_workflow(root, agent_id, run_id, task, &action.input).await
}
"blackboard.write" => {
observe_agent_runtime_blackboard_write(root, agent_id, run_id, &action.input)
}
@@ -103,7 +103,9 @@ pub(in crate::agent) fn game_creator_agent_runtime_tool_command_id(
"preview.validate" => Some("preview.validate"),
"image.inspect" => Some("image.inspect"),
"canvas.asset_generate" => Some("canvas.asset_generate"),
"ui.workflow.run" => Some("asset.register"),
"ui-design-doc.from-images" => Some("asset.register"),
"ui-design-doc.run-workflow" => Some("asset.register"),
"ui-design-doc.into-js" => Some("file.write"),
"blackboard.write" => Some("memory.write"),
"agent.message" => Some("conversation.write"),
"agent.delegate" => Some("agent.delegate"),
@@ -649,9 +649,6 @@ pub(crate) fn is_agent_runtime_project_mutation_observation(
if observation.tool == "project.patchset" {
return agent_runtime_patchset_advanced_project_revision(observation);
}
if observation.tool == "ui.workflow.run" {
return agent_runtime_ui_workflow_observation_advances_project_revision(observation);
}
if observation.tool == "canvas.asset_import" {
return observation
.detail
@@ -669,7 +666,6 @@ pub(crate) fn is_agent_runtime_project_mutation_observation(
| "project.patchset"
| "project.restore"
| "canvas.asset_generate"
| "ui.workflow.run"
)
}
@@ -761,47 +757,14 @@ pub(crate) fn agent_runtime_observation_advances_project_revision(
.and_then(|detail| serde_json::from_str::<serde_json::Value>(detail).ok())
.and_then(|value| value.get("revisionAdvanceCount")?.as_u64())
.is_some_and(|count| count > 0),
"ui.workflow.run" => {
agent_runtime_ui_workflow_observation_advances_project_revision(observation)
}
"memory.write" => true,
_ => false,
}
}
fn agent_runtime_ui_workflow_observation_advances_project_revision(
observation: &AgentRuntimeToolObservation,
) -> bool {
observation.tool == "ui.workflow.run"
&& observation.status == "ok"
&& observation
.detail
.as_deref()
.and_then(|detail| serde_json::from_str::<serde_json::Value>(detail).ok())
.and_then(|value| {
value
.get("revisionAdvanceCount")
.and_then(serde_json::Value::as_u64)
.map(|count| count > 0)
})
.unwrap_or(false)
}
fn agent_runtime_observation_project_revision_advance_count(
observation: &AgentRuntimeToolObservation,
) -> u64 {
if observation.tool == "ui.workflow.run" && observation.status == "ok" {
return observation
.detail
.as_deref()
.and_then(|detail| serde_json::from_str::<serde_json::Value>(detail).ok())
.and_then(|value| {
value
.get("revisionAdvanceCount")
.and_then(serde_json::Value::as_u64)
})
.unwrap_or(0);
}
if let Some(count) = observation
.detail
.as_deref()
@@ -821,19 +784,6 @@ fn agent_runtime_observation_project_revision_advance_count(
}
}
fn is_agent_runtime_ui_workflow_completed_observation(
observation: &AgentRuntimeToolObservation,
) -> bool {
observation.tool == "ui.workflow.run"
&& observation.status == "ok"
&& observation
.detail
.as_deref()
.and_then(|detail| serde_json::from_str::<serde_json::Value>(detail).ok())
.and_then(|value| value.get("completed").and_then(serde_json::Value::as_bool))
.unwrap_or(false)
}
pub(in crate::agent) fn is_agent_runtime_static_smoke_observation(
observation: &AgentRuntimeToolObservation,
) -> bool {
@@ -855,7 +805,6 @@ pub(in crate::agent) fn is_agent_runtime_project_verification_observation(
|| (observation.tool == "command.exec"
&& agent_runtime_command_exec_is_verification_eligible(observation))
|| (observation.tool == "canvas.asset_generate" && observation.status == "ok")
|| is_agent_runtime_ui_workflow_completed_observation(observation)
|| is_agent_runtime_static_smoke_observation(observation)
}
@@ -868,8 +817,6 @@ pub(in crate::agent) fn agent_runtime_project_verification_label(
"command.exec"
} else if observation.tool == "canvas.asset_generate" {
"canvas.asset_generate"
} else if observation.tool == "ui.workflow.run" {
"ui.workflow.run"
} else {
"project.verify"
}
@@ -817,17 +817,24 @@ mod tests {
use super::{
acquire_game_creator_agent_provider_plan_project_write_lock_with_wait,
agent_runtime_root_source_at, bind_game_creator_agent_runtime_run_profile_at,
build_game_creator_agent_background_final_reply_request,
build_game_creator_agent_background_tool_plan_request_locked,
game_creator_agent_context_preload_notice, init_local_game_project_at,
new_game_creation_app_seed_tasks,
game_creator_agent_context_preload_notice,
game_creator_agent_runtime_run_profile_binding_path,
game_creator_project_supervisor_chat_system_prompt, init_local_game_project_at,
new_game_creation_app_seed_tasks, provider_command_exec_contract,
provider_command_start_contract,
render_relaxed_autonomous_manifest_ready_task_background_prompt,
start_game_creator_agent_runtime_task_at, AgentRuntimeGoalContractAcceptanceNodeDraft,
AgentRuntimeGoalContractDraft, AgentRuntimeTaskLink, AgentRuntimeToolObservation,
required_runtime_prompt_section, start_game_creator_agent_runtime_task_at,
AgentRuntimeGoalContractAcceptanceNodeDraft, AgentRuntimeGoalContractDraft,
AgentRuntimeTaskLink, AgentRuntimeToolObservation, AgentRuntimeToolPlan,
AgentRuntimeToolPlanRequestSnapshot, GameCreatorLlmConfig,
AGENT_RUNTIME_AUTONOMOUS_PRE_MUTATION_LOOP_LIMIT, AGENT_RUNTIME_RESPOND_FUNCTION_NAME,
AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD, AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE,
AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE, AGENT_RUNTIME_UPDATE_PLAN_FUNCTION_NAME,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
AGENT_RUNTIME_AUTONOMOUS_PRE_MUTATION_LOOP_LIMIT,
AGENT_RUNTIME_COMPLETION_BLOCKER_TOOL_PLAN_PROTOCOL, AGENT_RUNTIME_RESPOND_FUNCTION_NAME,
AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD, AGENT_RUNTIME_RUN_PROFILE_STANDARD,
AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE, AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE,
AGENT_RUNTIME_UPDATE_PLAN_FUNCTION_NAME, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
RUNTIME_PROMPT_SUPERVISOR_CHAT_COMPOSITION,
};
fn build_game_creator_agent_background_tool_plan_request_for_test(
@@ -864,6 +871,24 @@ mod tests {
)
}
fn native_input_required_fields(
request: &platform_llm::LlmRunRequest,
tool: &str,
) -> Vec<String> {
let function_name = crate::agent_native_tools::native_runtime_function_name(tool)
.expect("native runtime function name");
request
.function_tools
.iter()
.find(|function| function.name == function_name)
.and_then(|function| function.parameters.pointer("/properties/input/required"))
.and_then(serde_json::Value::as_array)
.expect("native input required fields")
.iter()
.map(|field| field.as_str().expect("required field string").to_string())
.collect()
}
#[test]
fn relaxed_request_keeps_general_catalog_after_plan_rejection() {
let directory = crate::tests::canonical_test_tempdir("provider-plan-rejection-repair-");
@@ -1412,6 +1437,20 @@ mod tests {
assert!(!prompt.contains("本轮唯一可用工具是 agent.goal_contract"));
}
fn completion_blocker_protocol_requires_tool_repair_before_response() {
let protocol = AGENT_RUNTIME_COMPLETION_BLOCKER_TOOL_PLAN_PROTOCOL;
assert!(protocol.contains("最新 observation"));
assert!(protocol.contains("runtime.autonomous_completion"));
assert!(protocol.contains("status 为 blocked"));
assert!(protocol.contains("才能调用 respond_to_user"));
assert!(!protocol.contains("legacy response"));
assert!(protocol.contains("observation.detail 的 nextRequiredAction"));
assert!(protocol.contains("读取、修复和验证工具"));
assert!(protocol.contains("完成要求的动作、取得后续可信 observation"));
assert!(protocol.contains("通过完成门禁后"));
}
#[test]
fn supervisor_request_snapshot_preserves_prompt_visible_running_sibling_after_manifest_failure()
{
@@ -59,7 +59,9 @@ pub(crate) fn agent_runtime_executable_tools() -> Vec<&'static str> {
"preview.validate",
"image.inspect",
"canvas.asset_generate",
"ui.workflow.run",
"ui-design-doc.from-images",
"ui-design-doc.run-workflow",
"ui-design-doc.into-js",
"blackboard.write",
"agent.message",
"agent.delegate",
@@ -110,7 +112,7 @@ pub(crate) fn agent_runtime_acceptance_evidence_tools() -> BTreeSet<&'static str
"image.inspect",
"canvas.asset_generate",
"canvas.asset_import",
"ui.workflow.run",
"ui-design-doc.run-workflow",
]
.into_iter()
.collect()
@@ -302,7 +302,10 @@ pub(crate) use entrypoints::{
pub(crate) use finalization::resume_game_creator_agent_finalization_for_test_at;
pub(crate) use finalization::AgentRuntimePendingActionResume;
#[cfg(test)]
pub(crate) use interaction::agent_runtime_tool_requires_repository_context_fingerprint_gate;
pub(crate) use interaction::{
acquire_game_creator_agent_runtime_user_input_answer_locks_for_test,
agent_runtime_tool_requires_repository_context_fingerprint_gate,
};
pub(crate) use interaction::{
answer_game_creator_agent_runtime_user_input_at, confirm_game_creator_agent_runtime_task_at,
pending_repository_context_drift_observation, reject_game_creator_agent_runtime_task_at,
@@ -477,6 +477,20 @@ fn resolve_game_creator_agent_runtime_user_input_action_with_ordered_locks(
Ok((None, runtime_lock, resolved))
}
#[cfg(test)]
pub(crate) fn acquire_game_creator_agent_runtime_user_input_answer_locks_for_test(
root: &Path,
agent_id: &str,
run_id: &str,
action_id: &str,
) -> Result<(Option<ProjectWriteLock>, AgentRuntimeTaskLock), String> {
let (project_lock, runtime_lock, _) =
resolve_game_creator_agent_runtime_user_input_action_with_ordered_locks(
root, agent_id, run_id, action_id,
)?;
Ok((project_lock, runtime_lock))
}
pub(crate) fn answer_game_creator_agent_runtime_user_input_at(
root: &Path,
agent_id: &str,
@@ -137,7 +137,6 @@ fn acceptance_evidence_tool_may_advance_project_revision(tool: &str) -> bool {
| "project.patchset"
| "canvas.asset_import"
| "canvas.asset_generate"
| "ui.workflow.run"
)
}
@@ -11943,6 +11943,11 @@ impl ExternalGameplayJavascript {
.iter()
.any(|unit| unit.to_ascii_lowercase().contains(&marker))
}
#[cfg(test)]
pub(in crate::agent) fn module_units(&self) -> &[String] {
&self.module_units
}
}
fn normalize_javascript_module_analysis_sources(
@@ -162,7 +162,6 @@ pub(in crate::agent) fn validate_agent_runtime_verification_gate(
| "command.start"
| "canvas.asset_generate"
| "canvas.asset_import"
| "ui.workflow.run"
)
}) {
return Err("Agent Runtime verification gate 的修改工具无效".to_string());
@@ -176,7 +175,6 @@ pub(in crate::agent) fn validate_agent_runtime_verification_gate(
| "command.exec"
| "preview.validate"
| "canvas.asset_generate"
| "ui.workflow.run"
)
}) {
return Err("Agent Runtime verification gate 的验证工具无效".to_string());
@@ -19,7 +19,7 @@ mod process_ops;
mod project_ops;
mod run_status;
mod task_ops;
mod ui_workflow;
mod ui_design_doc;
pub(in crate::agent) use action_history::*;
pub(in crate::agent) use cocos_editor::*;
@@ -39,12 +39,14 @@ pub(in crate::agent) use process_ops::*;
pub(in crate::agent) use project_ops::*;
pub(in crate::agent) use run_status::*;
pub(in crate::agent) use task_ops::*;
pub(in crate::agent) use ui_workflow::*;
pub(in crate::agent) use ui_design_doc::*;
#[cfg(test)]
pub(crate) use delegation::observe_agent_runtime_agent_delegate_at_locked;
#[cfg(test)]
pub(crate) use delivery::build_static_delegate_result_for_child_at;
pub(crate) use delivery::{
build_static_delegate_result_for_child_at, wake_waiting_static_delegate_parent_run_for_test_at,
};
#[cfg(test)]
pub(crate) use isolated_joins::render_isolated_join_status_batch_with_limit;
#[cfg(test)]
@@ -408,6 +408,14 @@ pub(in crate::agent) fn wake_waiting_static_delegate_parent_run_at(
Ok(true)
}
#[cfg(test)]
pub(crate) fn wake_waiting_static_delegate_parent_run_for_test_at(
root: &Path,
parent_task: &AgentRuntimeTaskRecord,
) -> Result<bool, String> {
wake_waiting_static_delegate_parent_run_at(root, parent_task)
}
pub(in crate::agent) fn wake_waiting_autonomous_manifest_parent_run_at(
root: &Path,
parent_task: &AgentRuntimeTaskRecord,
@@ -0,0 +1,222 @@
//! UI 设计文档三个工具在 Runtime 侧的参数解析与调用。
//!
//! 工具名形如 `ui-design-doc.<动作>`;项目根目录、项目 ID 与 provider 身份一律由
//! Runtime 注入,模型只给设计图引用与目标文档 assetId。
use super::*;
use crate::ui_editor::agent_tools::{
create_ui_design_doc_from_images, run_ui_design_doc_workflow, CreateUiDesignDocFromImagesInput,
RunUiDesignDocWorkflowInput, UiDesignImageReference,
};
use crate::ui_editor::persistence::{generate_ui_design_code_at, GenerateUiDesignCodeInput};
use serde::Deserialize;
use serde_json::Value;
pub(in crate::agent) const UI_DESIGN_DOC_FROM_IMAGES_TOOL: &str = "ui-design-doc.from-images";
pub(in crate::agent) const UI_DESIGN_DOC_RUN_WORKFLOW_TOOL: &str = "ui-design-doc.run-workflow";
pub(in crate::agent) const UI_DESIGN_DOC_INTO_JS_TOOL: &str = "ui-design-doc.into-js";
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct FromImagesArguments {
images: Vec<UiDesignImageReference>,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct DesignDocArguments {
design_doc_asset_id: String,
}
pub(in crate::agent) fn observe_agent_runtime_ui_design_doc_from_images(
root: &Path,
input: &Value,
) -> AgentRuntimeToolObservation {
let tool = UI_DESIGN_DOC_FROM_IMAGES_TOOL;
let arguments = match serde_json::from_value::<FromImagesArguments>(input.clone()) {
Ok(arguments) => arguments,
Err(error) => return rejected(tool, format!("{tool} 输入无效:{error}")),
};
let expected_project_id = match game_creator_agent_runtime_context_project_id(root) {
Ok(project_id) => project_id,
Err(error) => return rejected(tool, error),
};
let revision_before = read_game_creator_agent_runtime_project_revision(root)
.ok()
.map(|snapshot| snapshot.revision);
match create_ui_design_doc_from_images(CreateUiDesignDocFromImagesInput {
project_path: root.to_string_lossy().into_owned(),
expected_project_id,
images: arguments.images,
}) {
Ok(created) => {
if revision_before.is_none_or(|before| created.committed_project_revision > before) {
emit_game_creator_manifest_invalidated(root, tool);
}
AgentRuntimeToolObservation {
tool: tool.to_string(),
status: "ok".to_string(),
summary: format!(
"已新建 UI 设计文档 {}:{} 张设计图,路径 {}",
created.asset.id,
created.image_ids.len(),
created.relative_path
),
detail: serde_json::to_string(&serde_json::json!({
"assetId": created.asset.id,
"relativePath": created.relative_path,
"imageIds": created.image_ids,
}))
.ok(),
}
}
Err(error) => error_observation(root, tool, error),
}
}
pub(in crate::agent) async fn observe_agent_runtime_ui_design_doc_run_workflow(
root: &Path,
agent_id: &str,
run_id: &str,
input: &Value,
) -> AgentRuntimeToolObservation {
let tool = UI_DESIGN_DOC_RUN_WORKFLOW_TOOL;
let arguments = match serde_json::from_value::<DesignDocArguments>(input.clone()) {
Ok(arguments) => arguments,
Err(error) => return rejected(tool, format!("{tool} 输入无效:{error}")),
};
let expected_project_id = match game_creator_agent_runtime_context_project_id(root) {
Ok(project_id) => project_id,
Err(error) => return rejected(tool, error),
};
let revision_before = read_game_creator_agent_runtime_project_revision(root)
.ok()
.map(|snapshot| snapshot.revision);
match run_ui_design_doc_workflow(
RunUiDesignDocWorkflowInput {
project_path: root.to_string_lossy().into_owned(),
expected_project_id,
asset_id: arguments.design_doc_asset_id,
},
Some((agent_id, run_id)),
)
.await
{
Ok(result) => {
if revision_before.is_none_or(|before| result.revision > before) {
emit_game_creator_manifest_invalidated(root, tool);
}
let pending = result.backfill_errors.len();
AgentRuntimeToolObservation {
tool: tool.to_string(),
status: "ok".to_string(),
summary: format!(
"UI 设计文档工作流完成:识别 {} 棵树,回填 {} 个节点,{} 个待人工处理{}",
result.recognized_tree_count,
result.bound_node_count,
result.problematic_node_count,
if pending > 0 {
format!("{pending} 项未回填")
} else {
String::new()
}
),
detail: serde_json::to_string(&result).ok(),
}
}
Err(error) => error_observation(root, tool, error),
}
}
pub(in crate::agent) fn observe_agent_runtime_ui_design_doc_into_js(
root: &Path,
input: &Value,
) -> AgentRuntimeToolObservation {
let tool = UI_DESIGN_DOC_INTO_JS_TOOL;
let arguments = match serde_json::from_value::<DesignDocArguments>(input.clone()) {
Ok(arguments) => arguments,
Err(error) => return rejected(tool, format!("{tool} 输入无效:{error}")),
};
let expected_project_id = match game_creator_agent_runtime_context_project_id(root) {
Ok(project_id) => project_id,
Err(error) => return rejected(tool, error),
};
match generate_ui_design_code_at(GenerateUiDesignCodeInput {
project_path: root.to_string_lossy().into_owned(),
expected_project_id,
asset_id: arguments.design_doc_asset_id,
}) {
Ok(result) => AgentRuntimeToolObservation {
tool: tool.to_string(),
status: "ok".to_string(),
summary: format!(
"已生成 UI 设计代码 {}:{} 棵树 / {} 个节点",
result.relative_path, result.tree_count, result.node_count
),
detail: serde_json::to_string(&result).ok(),
},
Err(error) => error_observation(root, tool, error),
}
}
/// 输入解析失败:模型只给了参数,摘要里不含宿主路径,无需再脱敏。
fn rejected(tool: &str, summary: String) -> AgentRuntimeToolObservation {
AgentRuntimeToolObservation {
tool: tool.to_string(),
status: "rejected".to_string(),
summary,
detail: None,
}
}
fn error_observation(root: &Path, tool: &str, error: String) -> AgentRuntimeToolObservation {
AgentRuntimeToolObservation {
tool: tool.to_string(),
status: "error".to_string(),
summary: redact_agent_runtime_project_paths(root, &error, 500),
detail: None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn from_images_rejects_unknown_fields_and_accepts_reference_pairs() {
let root = Path::new(".");
let rejected = observe_agent_runtime_ui_design_doc_from_images(
root,
&json!({ "images": [{ "assetId": "a", "path": null }], "extra": 1 }),
);
assert_eq!(rejected.status, "rejected");
assert!(rejected.summary.contains("extra"), "{}", rejected.summary);
// 参数合法但项目不存在:说明参数已经过解析,失败发生在项目校验之后。
let invalid_project = observe_agent_runtime_ui_design_doc_from_images(
root,
&json!({ "images": [{ "assetId": "a", "path": null }] }),
);
assert_eq!(invalid_project.status, "rejected");
assert!(!invalid_project.summary.contains("输入无效"));
}
#[test]
fn design_doc_asset_id_must_be_a_string() {
// run-workflow 与 into-js 共用同一份入参解析,这里解析一次、同步调用一次。
assert!(
serde_json::from_value::<DesignDocArguments>(json!({ "designDocAssetId": 1 })).is_err()
);
let observation = observe_agent_runtime_ui_design_doc_into_js(
Path::new("."),
&json!({ "designDocAssetId": 1 }),
);
assert_eq!(observation.status, "rejected");
assert!(
observation.summary.contains("输入无效"),
"{}",
observation.summary
);
}
}

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