把 UI 设计文档三个工具算进项目变更门禁
- 项目变更判定与项目版本号推进判定补上 UI 设计文档的三个工具:成功返回即算改过项目 - 只重写派生产物的代码生成工具不推进项目版本号,避免虚报推进量、把"项目被别处改动"的误报再造出来 - 新建文档与跑工作流这两个工具在观察明细里报出真实推进格数,失败路径也报,避免切图素材已登记却漏计 - 非法人写的读明细逻辑收敛成一个helper,三处调用共用 - 补六条门禁用例覆盖成功即算、只出代码不算推进、真实推进量优先、失败但已推进才算 - 同步共享记忆的项目概览与决策记录
This commit is contained in:
@@ -649,12 +649,16 @@ pub(crate) fn is_agent_runtime_project_mutation_observation(
|
||||
if observation.tool == "project.patchset" {
|
||||
return agent_runtime_patchset_advanced_project_revision(observation);
|
||||
}
|
||||
if agent_runtime_ui_design_doc_tool(&observation.tool) {
|
||||
// 三个 UI 设计文档工具都在返回时改项目:`from-images` 登记设计图与文档,`run-workflow` 登记
|
||||
// 切图并保存文档,`into-js` 重写 `ui/generated-*.js`。成功返回即算改过项目;失败路径只有明细
|
||||
// 报出真实推进量时才算,避免漏掉"调用失败但素材已经登记进去"。
|
||||
return observation.status == "ok"
|
||||
|| agent_runtime_observation_detail_revision_advance_count(observation)
|
||||
.is_some_and(|count| count > 0);
|
||||
}
|
||||
if observation.tool == "canvas.asset_import" {
|
||||
return observation
|
||||
.detail
|
||||
.as_deref()
|
||||
.and_then(|detail| serde_json::from_str::<serde_json::Value>(detail).ok())
|
||||
.and_then(|value| value.get("revisionAdvanceCount")?.as_u64())
|
||||
return agent_runtime_observation_detail_revision_advance_count(observation)
|
||||
.is_some_and(|count| count > 0);
|
||||
}
|
||||
observation.status == "ok"
|
||||
@@ -669,6 +673,25 @@ pub(crate) fn is_agent_runtime_project_mutation_observation(
|
||||
)
|
||||
}
|
||||
|
||||
/// UI 设计文档链路当前的三个 Agent 工具(`ui.workflow.run` 退役后由它们接管)。
|
||||
fn agent_runtime_ui_design_doc_tool(tool: &str) -> bool {
|
||||
matches!(
|
||||
tool,
|
||||
"ui-design-doc.from-images" | "ui-design-doc.run-workflow" | "ui-design-doc.into-js"
|
||||
)
|
||||
}
|
||||
|
||||
/// 明细里的真实项目 revision 推进量:登记类工具(`canvas.asset_import` 与 UI 设计文档工具)会写这个字段。
|
||||
fn agent_runtime_observation_detail_revision_advance_count(
|
||||
observation: &AgentRuntimeToolObservation,
|
||||
) -> Option<u64> {
|
||||
observation
|
||||
.detail
|
||||
.as_deref()
|
||||
.and_then(|detail| serde_json::from_str::<serde_json::Value>(detail).ok())
|
||||
.and_then(|value| value.get("revisionAdvanceCount")?.as_u64())
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn agent_runtime_command_start_advanced_project_revision(
|
||||
observation: &AgentRuntimeToolObservation,
|
||||
) -> bool {
|
||||
@@ -731,16 +754,17 @@ pub(crate) fn agent_runtime_observation_advances_project_revision(
|
||||
if agent_runtime_patchset_advanced_project_revision(observation) {
|
||||
return true;
|
||||
}
|
||||
if agent_runtime_ui_design_doc_tool(&observation.tool) {
|
||||
// `into-js` 只重写派生产物,不推进 revision;另外两个(登记设计图/文档、登记切图并写回)会推进。
|
||||
return matches!(
|
||||
observation.tool.as_str(),
|
||||
"ui-design-doc.from-images" | "ui-design-doc.run-workflow"
|
||||
) && (observation.status == "ok"
|
||||
|| agent_runtime_observation_detail_revision_advance_count(observation)
|
||||
.is_some_and(|count| count > 0));
|
||||
}
|
||||
if 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)
|
||||
})
|
||||
return agent_runtime_observation_detail_revision_advance_count(observation)
|
||||
.is_some_and(|count| count > 0);
|
||||
}
|
||||
match observation.tool.as_str() {
|
||||
@@ -751,12 +775,10 @@ pub(crate) fn agent_runtime_observation_advances_project_revision(
|
||||
| "project.restore"
|
||||
| "blackboard.write"
|
||||
| "canvas.asset_generate" => true,
|
||||
"canvas.asset_import" => observation
|
||||
.detail
|
||||
.as_deref()
|
||||
.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),
|
||||
"canvas.asset_import" => {
|
||||
agent_runtime_observation_detail_revision_advance_count(observation)
|
||||
.is_some_and(|count| count > 0)
|
||||
}
|
||||
"memory.write" => true,
|
||||
_ => false,
|
||||
}
|
||||
@@ -765,16 +787,7 @@ pub(crate) fn agent_runtime_observation_advances_project_revision(
|
||||
fn agent_runtime_observation_project_revision_advance_count(
|
||||
observation: &AgentRuntimeToolObservation,
|
||||
) -> u64 {
|
||||
if let Some(count) = 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)
|
||||
})
|
||||
{
|
||||
if let Some(count) = agent_runtime_observation_detail_revision_advance_count(observation) {
|
||||
return count;
|
||||
}
|
||||
if agent_runtime_observation_advances_project_revision(observation) {
|
||||
@@ -1777,3 +1790,107 @@ mod static_delegate_barrier_detail_gate_tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod ui_design_doc_project_mutation_gate_tests {
|
||||
use super::*;
|
||||
|
||||
fn observation(tool: &str, status: &str, detail: Option<&str>) -> AgentRuntimeToolObservation {
|
||||
AgentRuntimeToolObservation {
|
||||
tool: tool.to_string(),
|
||||
status: status.to_string(),
|
||||
summary: String::new(),
|
||||
detail: detail.map(str::to_string),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui_design_doc_tools_count_as_project_mutation_when_they_return() {
|
||||
for tool in [
|
||||
"ui-design-doc.from-images",
|
||||
"ui-design-doc.run-workflow",
|
||||
"ui-design-doc.into-js",
|
||||
] {
|
||||
let observation = observation(tool, "ok", None);
|
||||
assert!(
|
||||
is_agent_runtime_project_mutation_observation(&observation),
|
||||
"{tool} 成功返回后必须算改过项目"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_js_does_not_advance_project_revision() {
|
||||
let observation = observation("ui-design-doc.into-js", "ok", None);
|
||||
assert!(!agent_runtime_observation_advances_project_revision(
|
||||
&observation
|
||||
));
|
||||
assert_eq!(
|
||||
agent_runtime_observation_project_revision_advance_count(&observation),
|
||||
0,
|
||||
"只重写派生产物时不能虚报 revision 推进,否则会误报项目被别处改动"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registered_ui_design_doc_tools_advance_project_revision() {
|
||||
for tool in ["ui-design-doc.from-images", "ui-design-doc.run-workflow"] {
|
||||
let observation = observation(tool, "ok", None);
|
||||
assert!(agent_runtime_observation_advances_project_revision(
|
||||
&observation
|
||||
));
|
||||
assert_eq!(
|
||||
agent_runtime_observation_project_revision_advance_count(&observation),
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reported_revision_advance_count_is_used_verbatim() {
|
||||
let observation = observation(
|
||||
"ui-design-doc.run-workflow",
|
||||
"ok",
|
||||
Some(r#"{"revisionAdvanceCount":3}"#),
|
||||
);
|
||||
assert_eq!(
|
||||
agent_runtime_observation_project_revision_advance_count(&observation),
|
||||
3,
|
||||
"登记类工具在明细里报出的真实推进量必须优先于兜底的 1"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_ui_design_doc_tool_counts_only_when_it_already_advanced_revision() {
|
||||
let rolled_back = observation("ui-design-doc.from-images", "error", None);
|
||||
assert!(!is_agent_runtime_project_mutation_observation(&rolled_back));
|
||||
assert!(!agent_runtime_observation_advances_project_revision(
|
||||
&rolled_back
|
||||
));
|
||||
|
||||
let partially_advanced = observation(
|
||||
"ui-design-doc.run-workflow",
|
||||
"error",
|
||||
Some(r#"{"revisionAdvanceCount":2}"#),
|
||||
);
|
||||
assert!(is_agent_runtime_project_mutation_observation(
|
||||
&partially_advanced
|
||||
));
|
||||
assert!(agent_runtime_observation_advances_project_revision(
|
||||
&partially_advanced
|
||||
));
|
||||
assert_eq!(
|
||||
agent_runtime_observation_project_revision_advance_count(&partially_advanced),
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_project_tools_are_unaffected() {
|
||||
let observation = observation("file.read", "ok", Some(r#"{"lineCount":12}"#));
|
||||
assert!(!is_agent_runtime_project_mutation_observation(&observation));
|
||||
assert!(!agent_runtime_observation_advances_project_revision(
|
||||
&observation
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ pub(in crate::agent) async fn observe_agent_runtime_ui_design_doc_from_images(
|
||||
};
|
||||
match created {
|
||||
Ok(created) => {
|
||||
let revision_advance = revision_before
|
||||
.map(|before| created.committed_project_revision.saturating_sub(before));
|
||||
if revision_before.is_none_or(|before| created.committed_project_revision > before) {
|
||||
emit_game_creator_manifest_invalidated(root, tool);
|
||||
}
|
||||
@@ -80,11 +82,17 @@ pub(in crate::agent) async fn observe_agent_runtime_ui_design_doc_from_images(
|
||||
"assetId": created.asset.id,
|
||||
"relativePath": created.relative_path,
|
||||
"imageIds": created.image_ids,
|
||||
"revisionAdvanceCount": revision_advance,
|
||||
}))
|
||||
.ok(),
|
||||
}
|
||||
}
|
||||
Err(error) => error_observation(root, tool, error),
|
||||
Err(error) => error_observation_with_revision_advance(
|
||||
root,
|
||||
tool,
|
||||
error,
|
||||
project_revision_advance_since(root, revision_before),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +130,9 @@ pub(in crate::agent) async fn observe_agent_runtime_ui_design_doc_run_workflow(
|
||||
let revision_after = read_game_creator_agent_runtime_project_revision(root)
|
||||
.ok()
|
||||
.map(|snapshot| snapshot.revision);
|
||||
let revision_advance = revision_before
|
||||
.zip(revision_after)
|
||||
.map(|(before, after)| after.saturating_sub(before));
|
||||
if revision_before
|
||||
.is_none_or(|before| revision_after.is_none_or(|after| after > before))
|
||||
{
|
||||
@@ -142,10 +153,15 @@ pub(in crate::agent) async fn observe_agent_runtime_ui_design_doc_run_workflow(
|
||||
String::new()
|
||||
}
|
||||
),
|
||||
detail: serde_json::to_string(&result).ok(),
|
||||
detail: workflow_detail_with_revision_advance(&result, revision_advance),
|
||||
}
|
||||
}
|
||||
Err(error) => error_observation(root, tool, error),
|
||||
Err(error) => error_observation_with_revision_advance(
|
||||
root,
|
||||
tool,
|
||||
error,
|
||||
project_revision_advance_since(root, revision_before),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +228,45 @@ fn error_observation(root: &Path, tool: &str, error: String) -> AgentRuntimeTool
|
||||
}
|
||||
}
|
||||
|
||||
/// 失败路径也可能已经改过项目(切图素材先登记、后面的步骤才失败,且按约定不回滚),所以重读一次项目
|
||||
/// 版本号,把真实推进量写进明细交给门禁;推进量为 0 时不写字段,明细保持原样。
|
||||
fn project_revision_advance_since(root: &Path, revision_before: Option<u64>) -> u64 {
|
||||
let Some(before) = revision_before else {
|
||||
return 0;
|
||||
};
|
||||
read_game_creator_agent_runtime_project_revision(root)
|
||||
.map(|snapshot| snapshot.revision.saturating_sub(before))
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn error_observation_with_revision_advance(
|
||||
root: &Path,
|
||||
tool: &str,
|
||||
error: String,
|
||||
revision_advance: u64,
|
||||
) -> AgentRuntimeToolObservation {
|
||||
let mut observation = error_observation(root, tool, error);
|
||||
if revision_advance > 0 {
|
||||
observation.detail =
|
||||
Some(serde_json::json!({ "revisionAdvanceCount": revision_advance }).to_string());
|
||||
}
|
||||
observation
|
||||
}
|
||||
|
||||
/// 工作流结果明细原样保留,只追加一项真实推进量,供项目变更门禁读取。
|
||||
fn workflow_detail_with_revision_advance<T: serde::Serialize>(
|
||||
result: &T,
|
||||
revision_advance: Option<u64>,
|
||||
) -> Option<String> {
|
||||
let mut value = serde_json::to_value(result).ok()?;
|
||||
if let Some(revision_advance) = revision_advance {
|
||||
value
|
||||
.as_object_mut()?
|
||||
.insert("revisionAdvanceCount".to_string(), revision_advance.into());
|
||||
}
|
||||
serde_json::to_string(&value).ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -9275,6 +9275,15 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- 影响面:`apps/ai-game-creator-shell/src-tauri/src/{main.rs,ui_editor/**}`、`src/features/ui-editor/**`、`src/view/ui-editor/**`、`src/view/project-development/index.tsx`、`tests/{uiEditorPage,uiEditorState,previewWorkspaceZoom}.test.*`、`docs/technical/【技术方案】UI编辑器代码地图与模块职责-2026-09-23.md`、`docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`、`docs/technical/【设计】UI编辑器工作流完成通知弹窗-2026-09-04.md`、`docs/technical/【前端架构】UI编辑会话模块边界-2026-08-19.md`。
|
||||
- 验证:`cargo check` 与 `cargo test --bin genarrative-ai-game-creator-shell ui_editor`(160 passed)通过,ts-rs 重新导出 `types/UIDesignImage.ts` 并删除三个已退役类型;`npx vitest run` 定向 `uiEditorPage` / `uiEditorState` / `uiDesignStateStore` / `previewWorkspaceZoom` / `appSurface` 全绿;AGC `tsc --noEmit`、改动文件 eslint、`cargo fmt --check`、`check:encoding`、`git diff --check` 通过。整套 Rust 测试在本容器仍有 60 条环境性失败(`/sbin -> usr/bin` 让 `command.exec` 沙箱 merged-usr 预检失败),与本次改动无关。
|
||||
|
||||
## 2026-09-24 UI 设计文档三个工具纳入项目变更门禁(成功返回即算改过项目)
|
||||
|
||||
- 背景:`ui.workflow.run` 退役时,项目变更门禁的两处工具名单(`agent/runtime_actions/project_gates.rs` 的 `is_agent_runtime_project_mutation_observation` 与 `agent_runtime_observation_advances_project_revision`)只删未补,新接管的 `ui-design-doc.from-images` / `ui-design-doc.run-workflow` / `ui-design-doc.into-js` 都没登记。后果:改完项目可能被判定"没改过",于是不要求验证就判完成、自动模式的 liveness 判据看不到进展;`agent_runtime_pending_expected_project_revision` 少算推进量又会误报 `pending_project_revision_drift`("并行项目变更使旧动作过期")。
|
||||
- 口径(产品确认):三个工具都在**成功返回时**改项目——`from-images` 登记设计图与文档、`run-workflow` 登记切图并保存文档、`into-js` 重写 `ui/generated-*.js`;调用中途不产生需要门禁额外追踪的中间态。
|
||||
- 决策:门禁按"工具名 + 成功返回"判定三者都算项目变更;其中 `into-js` 只重写派生产物、**不推进 revision**,所以不进 revision 推进名单——进去会虚报推进量,正好把要修的误报再造出来。
|
||||
- 决策(真实推进量):`from-images` / `run-workflow` 的观察明细带上 `revisionAdvanceCount`(沿用 `canvas.asset_import` 的既有字段),失败路径也带——切图素材先登记、后面步骤才失败时按约定不回滚,仍要如实计数,避免门禁把真实推进当成"别处改动"。
|
||||
- 代价与取舍:失败路径要多读一次项目 revision;`into-js` 属于"改了东西但项目 revision 没动"的少数派,与写文件类工具口径一致。
|
||||
- 验证方式:新增 `project_gates::ui_design_doc_project_mutation_gate_tests` 六条用例(成功即算变更、`into-js` 不推进 revision、登记类兜底推进量为 1、明细里的真实推进量优先、失败但已推进才算、无关工具不受影响)。
|
||||
|
||||
## 2026-09-23 UI 编辑器 Agent 工具化重写
|
||||
|
||||
- 背景:UI 编辑器的 Agent 链路原本只有一个 `ui.workflow.run`,把发现页面、桥接设计图、结构识别、多树合并、组件绑定、finalize 全塞进一个工具,工具参数本身就是工作流状态;识别与切分的产物由前端 `useUiEditorPage.ts` 落 State 再保存,Agent 侧没有任何恢复点,任一步失败只能整轮重来。
|
||||
|
||||
@@ -67,7 +67,7 @@ SpacetimeDB crate、SDK、CLI / standalone 与生成 bindings 按 `2.8.3` 对齐
|
||||
- 2026-09-09 起,AGC 已新增遵循 OpenAI Agent Plugins 组合模型的通用 Plugin Host/SDK:Plugin、Skill 和 MCP 进入统一扩展 catalog;插件生命周期、行分隔 JSON-RPC、UI 面板、Capability Registry、权限和审计由 `plugin_host` 统一承接,Skill/MCP 仍分别交给各自现有 loader/transport;目标编辑器只通过通用 `EditorAdapter` 扩展点接入。详见 `docs/technical/【技术方案】AGC通用插件宿主与编辑器适配-2026-09-09.md`。
|
||||
- Unity 编辑器能力以 `plugins/agc-unity-editor` 内置插件提供,固定复用 Apache-2.0 的 DotCraft Attach 核心;Windows x64 / Unity Mono 接入不安装项目包。GUI、Runtime 与 DirectProject 通过现有 Runner 统一执行归属,跨进程回执与持久不确定阻断统一处理。首次打开 Unity 工程只初始化 AGC `.agent` 元数据,保留原引擎工程;详见 `docs/technical/【技术方案】AGC Unity编辑器插件接入-2026-09-18.md`。
|
||||
- DirectProject 的 Codex 原生文件、搜索、命令、图片查看和 Skill 仅在用户项目 cwd 与 `workspaceWrite(writableRoots=[project])` 内可用;原生命令允许联网以支持 npm 安装,npm 缓存位于项目内 `.npm-cache/`。多 Agent、Apps、插件、hooks、图片生成、Goals、Workspace Dependencies、Tool Suggestion 和原生浏览器/电脑控制保持关闭。app-server 使用隔离 `CODEX_HOME`,provider 凭据只由 AGC 客户端代理持有,不能进入模型上下文或 shell 环境。
|
||||
- `ui-prototype`(设计图片)与 UI 编辑器 `ui-design-doc` JSON 是不同资源。Agent 只通过三个工具驱动:`ui-design-doc.from-images` 由一至四张已登记设计图新建并登记文档(文档内设计图身份即图片 assetId),`ui-design-doc.run-workflow` 在 Rust 内跑 `recognize → separate → write-back` 并写回 State/revision,`ui-design-doc.into-js` 产出 `ui/generated-*.js`(不推进 revision);工具名与入参文案在 `prompts/runtime/texts/ui-design-doc.json`。Provider 缺失、请求失败、工具缺失或结果不匹配时保留真实 State 并返回错误,不得用 deterministic seed 伪造完成;旧 `ui.workflow.run`、多树合并、组件绑定与原型幂等桥接已退役,不保留兼容入口。
|
||||
- `ui-prototype`(设计图片)与 UI 编辑器 `ui-design-doc` JSON 是不同资源。Agent 只通过三个工具驱动:`ui-design-doc.from-images` 由一至四张已登记设计图新建并登记文档(文档内设计图身份即图片 assetId),`ui-design-doc.run-workflow` 在 Rust 内跑 `recognize → separate → write-back` 并写回 State/revision,`ui-design-doc.into-js` 产出 `ui/generated-*.js`(不推进 revision);三个工具都算项目变更观察(成功返回即算改过项目),其中只有前两个推进项目 revision;工具名与入参文案在 `prompts/runtime/texts/ui-design-doc.json`。Provider 缺失、请求失败、工具缺失或结果不匹配时保留真实 State 并返回错误,不得用 deterministic seed 伪造完成;旧 `ui.workflow.run`、多树合并、组件绑定与原型幂等桥接已退役,不保留兼容入口。
|
||||
- UI workflow 的资源桥接与 Runtime 边界以 `docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md` 和 AGC 实施计划的 2026-08-24 覆盖段为准;只生成图片、登记空 JSON 或进入普通图片画布都不构成 workflow 完成。
|
||||
|
||||
## 当前产品边界
|
||||
|
||||
Reference in New Issue
Block a user