From 51e35468a53ba2da30df800441a67eda57e93421 Mon Sep 17 00:00:00 2001 From: kdletters Date: Wed, 12 Aug 2026 11:08:53 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=87=AA=E4=B8=BB=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E6=B5=8B=E8=AF=95=E9=A1=B9=E7=9B=AE=E9=94=81=E7=AB=9E?= =?UTF-8?q?=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将纯继续契约测试改为异步并等待子任务持久化终态。 等待 Agent lane 释放后再执行后续 manifest 写入,避免后台终态投影竞争项目锁。 --- .../autonomous_completion_contract_tests.rs | 16 +++++- .../src-tauri/src/tests/mod.rs | 54 ++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs index 6cbc7c149..360c24f46 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs @@ -925,8 +925,8 @@ fn autonomous_continuation_intent_is_exact_and_does_not_swallow_new_requirements } } -#[test] -fn game_chat_pure_continue_inherits_failed_root_semantics_and_manifest_progress() { +#[tokio::test(flavor = "current_thread")] +async fn game_chat_pure_continue_inherits_failed_root_semantics_and_manifest_progress() { let _config_guard = crate::tests::write_test_local_config("{}".to_string()); let original_task = "做一个水晶主题的俄罗斯方块,完成移动、旋转、消行和重开"; let (_temporary, root, original_state, original_contract) = autonomous_fixture_with_source( @@ -1093,6 +1093,18 @@ fn game_chat_pure_continue_inherits_failed_root_semantics_and_manifest_progress( .expect("continued root must pass the scheduler contract gate"); assert_eq!(scheduled.len(), 1); assert_eq!(scheduled[0].state.agent_id, "code-prototype"); + // Scheduling starts the child worker asynchronously. Wait for its + // durable terminal projection and lane release before mutating the same + // project; a lane release alone can precede a late manifest projection. + let _ = crate::tests::wait_for_agent_runtime_manifest_projection_async( + &root, + "code-prototype", + &scheduled[0].state.run_id, + "failed", + "budget-exhausted", + GameCreationAppTaskStatus::Failed, + ) + .await; update_manifest_task_status_at( &root, "code-prototype", diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs index 743fb95d8..d0fe0840b 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/mod.rs @@ -475,7 +475,7 @@ fn wait_for_agent_runtime_terminal_and_lane_release( ); } -async fn wait_for_agent_runtime_terminal_and_lane_release_async( +pub(crate) async fn wait_for_agent_runtime_terminal_and_lane_release_async( root: &Path, agent_id: &str, run_id: &str, @@ -526,6 +526,58 @@ async fn wait_for_agent_runtime_terminal_and_lane_release_async( } } +pub(crate) async fn wait_for_agent_runtime_manifest_projection_async( + root: &Path, + agent_id: &str, + run_id: &str, + runtime_status: &str, + phase: &str, + manifest_status: GameCreationAppTaskStatus, +) -> AgentRuntimeResult { + let deadline = Instant::now() + Duration::from_secs(10); + let mut terminal = wait_for_agent_runtime_terminal_and_lane_release_async( + root, agent_id, run_id, runtime_status, phase, + ) + .await; + let mut stable_samples = 0_u8; + loop { + if let Ok(project_lock) = acquire_game_creator_agent_runtime_project_write_lock_with_wait( + root, + "test.wait_runtime_manifest_projection", + ) { + let manifest = read_manifest_for_project(root) + .expect("read manifest while waiting for terminal projection"); + let projected = manifest + .tasks + .iter() + .find(|task| task.id == agent_id) + .is_some_and(|task| task.status == manifest_status); + let reread = read_game_creator_agent_runtime_at(root, agent_id) + .expect("reread runtime while waiting for terminal projection"); + drop(project_lock); + if projected + && reread.state.run_id == run_id + && reread.state.status == runtime_status + && reread.state.phase == phase + { + stable_samples = stable_samples.saturating_add(1); + terminal = reread; + if stable_samples >= 2 { + return terminal; + } + } else { + stable_samples = 0; + terminal = reread; + } + } + assert!( + Instant::now() < deadline, + "runtime terminal manifest projection did not settle for {agent_id}/{run_id}" + ); + tokio::time::sleep(Duration::from_millis(20)).await; + } +} + async fn wait_for_agent_runtime_lane_release_async( root: &Path, agent_id: &str,