From 3be91bf2831dcba3015476a4eb7b47a140ad2922 Mon Sep 17 00:00:00 2001 From: Linghong Date: Wed, 9 Sep 2026 03:44:33 +0000 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Drust=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src-tauri/src/agent/generation.rs | 6 +- .../src/agent/generation/canvas_generation.rs | 66 ++++++++++++++----- .../src-tauri/src/agent/prompt.rs | 3 +- .../src/agent/runtime_tools/media.rs | 23 ++++--- .../src-tauri/src/tests/command_runtime.rs | 3 +- .../src-tauri/src/tests/configuration.rs | 36 ++++++---- .../src-tauri/src/tests/project.rs | 3 +- 7 files changed, 98 insertions(+), 42 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/generation.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/generation.rs index e6a48c95f..a83b6dba4 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/generation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/generation.rs @@ -24,12 +24,12 @@ pub(crate) use canvas_generation::{ ExternalGenerationInitialResponse, }; pub(in crate::agent) use canvas_generation::{ - recover_persisted_visual_generation_options, commit_prepared_platform_art_asset_at, commit_prepared_platform_art_asset_strict_slices_at, generate_platform_art_asset_with_retained_runtime_options_at, generate_platform_art_asset_with_runtime_options_at, - platform_art_generation_error_result_unknown, register_existing_platform_art_slices_at, - request_platform_art_asset_with_runtime_options_at, restore_platform_art_asset_bytes_at, + platform_art_generation_error_result_unknown, recover_persisted_visual_generation_options, + register_existing_platform_art_slices_at, request_platform_art_asset_with_runtime_options_at, + restore_platform_art_asset_bytes_at, retained_platform_art_generation_runtime_spritesheet_identity_at, retained_platform_art_generation_runtime_state_matches_direct_stage_at, validate_platform_art_png_bytes_with_limits, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs index 6ca24ad6f..8d098b8e6 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs @@ -441,19 +441,37 @@ pub(in crate::agent) fn recover_persisted_visual_generation_options( }; let (path, ratio, size, kind, label, generation_kind) = match context.agent_id.as_str() { "art-director" => ( - "assets/art-spec.png", "1:1", "1K", "icon-spec", "游戏统一视觉规范图", "spec", + "assets/art-spec.png", + "1:1", + "1K", + "icon-spec", + "游戏统一视觉规范图", + "spec", ), "design-foundation" => ( - "assets/ui-prototype.png", "16:9", "2K", "ui-prototype", "游戏横屏界面原型图", "ui-design", + "assets/ui-prototype.png", + "16:9", + "2K", + "ui-prototype", + "游戏横屏界面原型图", + "ui-design", ), "art-asset-plan" => ( - "assets/art-spritesheet.png", "1:1", "1K", "art-spritesheet", "游戏首版核心美术素材", "icon-spritesheet", + "assets/art-spritesheet.png", + "1:1", + "1K", + "art-spritesheet", + "游戏首版核心美术素材", + "icon-spritesheet", ), _ => return Ok(None), }; let mut options = requested.clone(); let label = if context.agent_id == "design-foundation" - && options.output_path.as_deref().is_some_and(design_foundation_ui_page_output_path_is_valid) + && options + .output_path + .as_deref() + .is_some_and(design_foundation_ui_page_output_path_is_valid) { "游戏功能页面设计图" } else { @@ -481,7 +499,9 @@ pub(in crate::agent) fn recover_persisted_visual_generation_options( && options.image_size == size && (options.output_path.as_deref() == Some(path) || (context.agent_id == "design-foundation" - && options.output_path.as_deref() + && options + .output_path + .as_deref() .is_some_and(design_foundation_ui_page_output_path_is_valid))) { Ok(Some(options)) @@ -10227,7 +10247,8 @@ mod canvas_generation_tests { } #[tokio::test] - async fn accepted_runtime_generation_checks_running_operation_before_rejecting_changed_intent() { + async fn accepted_runtime_generation_checks_running_operation_before_rejecting_changed_intent() + { let temporary = tempfile::tempdir().expect("create changed-intent recovery project"); let root = temporary.path(); init_local_game_project_at(root, "changed-intent", "旧异步生成意图隔离") @@ -10345,8 +10366,7 @@ mod canvas_generation_tests { 1, "only the existing operation may be queried" ); - assert!(requests[0] - .starts_with("GET /api/external/v1/generations/old-operation ")); + assert!(requests[0].starts_with("GET /api/external/v1/generations/old-operation ")); assert!(game_creator_agent_runtime_external_generation_exists( root, &runtime_context.agent_id, @@ -10355,7 +10375,8 @@ mod canvas_generation_tests { } #[tokio::test] - async fn accepted_derived_generation_checks_running_operation_before_rejecting_changed_art_spec() { + async fn accepted_derived_generation_checks_running_operation_before_rejecting_changed_art_spec( + ) { let temporary = tempfile::tempdir().expect("create changed reference project"); let root = temporary.path(); init_local_game_project_at(root, "changed-reference", "规范图身份隔离") @@ -10527,7 +10548,9 @@ mod canvas_generation_tests { "{error}" ); assert!(error.contains("当前规范图身份"), "{error}"); - stop_sender.send(()).expect("stop changed reference fixture"); + stop_sender + .send(()) + .expect("stop changed reference fixture"); server.join().expect("join changed reference fixture"); let requests = request_receiver.try_iter().collect::>(); assert_eq!( @@ -10535,8 +10558,9 @@ mod canvas_generation_tests { 1, "only the existing operation may be queried" ); - assert!(requests[0] - .starts_with("GET /api/external/v1/generations/old-background-operation ")); + assert!( + requests[0].starts_with("GET /api/external/v1/generations/old-background-operation ") + ); assert!(game_creator_agent_runtime_external_generation_exists( root, &runtime_context.agent_id, @@ -10944,13 +10968,23 @@ mod canvas_generation_tests { Some(options.clone()) ); assert!(recover_persisted_visual_generation_options( - root, &pending, "显式改变生成意图", &sparse_options, - ).unwrap().is_none()); + root, + &pending, + "显式改变生成意图", + &sparse_options, + ) + .unwrap() + .is_none()); let mut explicit_options = sparse_options.clone(); explicit_options.asset_kind = "game-background".to_string(); assert!(recover_persisted_visual_generation_options( - root, &pending, prompt, &explicit_options, - ).unwrap().is_none()); + root, + &pending, + prompt, + &explicit_options, + ) + .unwrap() + .is_none()); resume_game_creator_agent_background_tasks_at(root) .expect("resume accepted generation through recovery scan"); diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs index e70b332e0..198940a5e 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs @@ -1827,7 +1827,8 @@ mod tests { let with_canvas = game_creator_art_director_tool_plan_prompt("shared runtime contract", true); - assert!(with_canvas.contains("canvas.asset_generate 的 assetKind、outputPath、尺寸、比例和提示词")); + assert!(with_canvas + .contains("canvas.asset_generate 的 assetKind、outputPath、尺寸、比例和提示词")); assert!(with_canvas.contains("需要参考图时使用已登记资源 ID")); assert!(with_canvas.contains("生成后核对返回资源、权限、计费和登记状态")); } diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/media.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/media.rs index 728bb2f75..e570d026c 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/media.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/media.rs @@ -555,15 +555,22 @@ pub(in crate::agent) async fn observe_agent_runtime_platform_art_asset_generatio slice_count, }; if let Some(pending) = pending_action { - match recover_persisted_visual_generation_options(root, pending, &prompt, &requested_options) { + match recover_persisted_visual_generation_options( + root, + pending, + &prompt, + &requested_options, + ) { Ok(Some(recovered)) => requested_options = recovered, - Ok(None) => {}, - Err(error) => return AgentRuntimeToolObservation { - tool: "canvas.asset_generate".to_string(), - status: AGENT_RUNTIME_TOOL_OBSERVATION_STATUS_NEEDS_RECONCILIATION.to_string(), - summary: redact_agent_runtime_project_paths(root, &error, 240), - detail: None, - }, + Ok(None) => {} + Err(error) => { + return AgentRuntimeToolObservation { + tool: "canvas.asset_generate".to_string(), + status: AGENT_RUNTIME_TOOL_OBSERVATION_STATUS_NEEDS_RECONCILIATION.to_string(), + summary: redact_agent_runtime_project_paths(root, &error, 240), + detail: None, + } + } } } let mut options = { diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/command_runtime.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/command_runtime.rs index 27fe495ba..bb710da12 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/command_runtime.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/command_runtime.rs @@ -2970,7 +2970,8 @@ fn limited_local_command_rejects_blank_canvas_game_smoke() { #[test] fn limited_local_command_rejects_invalid_javascript_before_surface_checks() { let root = unique_project_path(); - init_existing_html_project_at(&root, "project-1", "脚本语法优先验证测试").expect("project init"); + init_existing_html_project_at(&root, "project-1", "脚本语法优先验证测试") + .expect("project init"); let html = r#" diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/configuration.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/configuration.rs index 8bb6d2f70..f5be621e0 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/configuration.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/configuration.rs @@ -813,19 +813,25 @@ fn app_config_save_updates_conflicting_local_overlay() { fs::create_dir_all(&root).expect("config dir"); let _guard = use_test_runtime_config_dir(root.clone()); let overlay_path = root.join(GAME_CREATOR_LOCAL_CONFIG_FILE_NAME); - fs::write(&overlay_path, r#"{"llm":{"reasoningEffort":"low"},"selectedModelId":"existing","custom":{"keep":true}}"#) - .expect("write overlay"); + fs::write( + &overlay_path, + r#"{"llm":{"reasoningEffort":"low"},"selectedModelId":"existing","custom":{"keep":true}}"#, + ) + .expect("write overlay"); let mut config = load_game_creator_app_config().expect("load config"); config.llm.reasoning_effort = "high".to_string(); let saved = write_game_creator_app_config(config).expect("save config"); let effective = load_game_creator_app_config().expect("effective config"); assert_eq!(saved.config.llm.reasoning_effort, "high"); assert_eq!(effective.llm.reasoning_effort, "high"); - let overlay: serde_json::Value = serde_json::from_str( - &fs::read_to_string(&overlay_path).expect("read overlay"), - ).expect("parse overlay"); + let overlay: serde_json::Value = + serde_json::from_str(&fs::read_to_string(&overlay_path).expect("read overlay")) + .expect("parse overlay"); assert_eq!(overlay["selectedModelId"], "existing"); - assert_eq!(overlay["llm"], serde_json::json!({"reasoningEffort": "high"})); + assert_eq!( + overlay["llm"], + serde_json::json!({"reasoningEffort": "high"}) + ); assert_eq!(overlay["custom"]["keep"], true); fs::remove_dir_all(root).expect("cleanup config dir"); } @@ -836,16 +842,22 @@ fn app_config_model_selection_only_updates_model_overlay() { fs::create_dir_all(&root).expect("config dir"); let _guard = use_test_runtime_config_dir(root.clone()); let overlay_path = root.join(GAME_CREATOR_LOCAL_CONFIG_FILE_NAME); - fs::write(&overlay_path, r#"{"selectedModelId":"old","llm":{"reasoningEffort":"low"}}"#) - .expect("write overlay"); + fs::write( + &overlay_path, + r#"{"selectedModelId":"old","llm":{"reasoningEffort":"low"}}"#, + ) + .expect("write overlay"); let saved = select_game_creator_model("new-model".to_string()).expect("select model"); let effective = load_game_creator_app_config().expect("effective config"); assert_eq!(saved.config.selected_model_id, "new-model"); assert_eq!(effective.selected_model_id, "new-model"); - let overlay: serde_json::Value = serde_json::from_str( - &fs::read_to_string(&overlay_path).expect("read overlay"), - ).expect("parse overlay"); - assert_eq!(overlay["llm"], serde_json::json!({"reasoningEffort": "low"})); + let overlay: serde_json::Value = + serde_json::from_str(&fs::read_to_string(&overlay_path).expect("read overlay")) + .expect("parse overlay"); + assert_eq!( + overlay["llm"], + serde_json::json!({"reasoningEffort": "low"}) + ); fs::remove_dir_all(root).expect("cleanup config dir"); } diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs index 0d0a7b80f..c65090353 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs @@ -4800,7 +4800,8 @@ fn stale_preview_cleanup_does_not_stop_a_newer_matching_project_server() { #[test] fn stale_preview_cleanup_cannot_be_blocked_by_project_stop_policy() { let root = unique_project_path(); - init_existing_html_project_at(&root, "project-1", "预览补偿清理策略测试").expect("project init"); + init_existing_html_project_at(&root, "project-1", "预览补偿清理策略测试") + .expect("project init"); write_project_permission_policy_at( &root, ProjectPermissionPolicy {