From 27f66bf0b18ba4827382dd9b5b07bcc0863388cc Mon Sep 17 00:00:00 2001 From: kdletters Date: Wed, 22 Jul 2026 16:42:20 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A8=B3=E5=AE=9AAI=E5=8E=9F=E7=94=9F=E5=A3=B3?= =?UTF-8?q?=E7=BB=88=E6=80=81=E5=AE=A1=E8=AE=A1=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 让最终回复失败测试等待 Runtime 终态与 Agent lane 完整释放 让并行后台任务测试在读取对话和审计前等待两个 Agent lane 释放 补充异步终态测试的具体回归用例说明 --- .../src-tauri/src/tests.rs | 63 +++++++------------ docs/project-memory/shared-memory/pitfalls.md | 2 +- 2 files changed, 25 insertions(+), 40 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests.rs b/apps/ai-game-creator-shell/src-tauri/src/tests.rs index 19e17c90d..0bfff5e40 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests.rs @@ -17764,18 +17764,14 @@ async fn background_agent_runtime_marks_response_plan_step_failed_when_final_rep ) .expect("start background task"); - let mut runtime = read_game_creator_agent_runtime_at(&root, "design-director") - .expect("read runtime") - .state; - for _ in 0..750 { - if runtime.status == "failed" { - break; - } - std::thread::sleep(Duration::from_millis(20)); - runtime = read_game_creator_agent_runtime_at(&root, "design-director") - .expect("read runtime") - .state; - } + let failed_result = wait_for_agent_runtime_terminal_and_lane_release( + &root, + "design-director", + "design-response-fail-run", + "failed", + "failed", + ); + let runtime = &failed_result.state; assert_eq!(runtime.status, "failed"); assert_eq!(runtime.phase, "failed"); @@ -17787,8 +17783,6 @@ async fn background_agent_runtime_marks_response_plan_step_failed_when_final_rep .detail .as_deref() .is_some_and(|detail| detail.contains("后台 Agent 最终回复调用 LLM 失败"))); - let failed_result = - read_game_creator_agent_runtime_at(&root, "design-director").expect("read failed events"); let event_types = failed_result .recent_events .iter() @@ -46074,24 +46068,22 @@ async fn background_agent_runtime_tasks_can_run_in_parallel_and_persist_replies( assert!(combined_requests.contains("后台准备主角规范图")); assert!(combined_requests.contains("后台整理玩法循环")); - let mut art_runtime = read_game_creator_agent_runtime_at(&root, "art-director") - .expect("read art runtime") - .state; - let mut design_runtime = read_game_creator_agent_runtime_at(&root, "design-director") - .expect("read design runtime") - .state; - for _ in 0..250 { - if art_runtime.status == "idle" && design_runtime.status == "idle" { - break; - } - std::thread::sleep(Duration::from_millis(20)); - art_runtime = read_game_creator_agent_runtime_at(&root, "art-director") - .expect("read art runtime") - .state; - design_runtime = read_game_creator_agent_runtime_at(&root, "design-director") - .expect("read design runtime") - .state; - } + let art_runtime_result = wait_for_agent_runtime_terminal_and_lane_release( + &root, + "art-director", + "art-background-run", + "idle", + "completed", + ); + let design_runtime_result = wait_for_agent_runtime_terminal_and_lane_release( + &root, + "design-director", + "design-background-run", + "idle", + "completed", + ); + let art_runtime = &art_runtime_result.state; + let design_runtime = &design_runtime_result.state; assert_eq!(art_runtime.status, "idle"); assert_eq!(art_runtime.phase, "completed"); @@ -46105,10 +46097,6 @@ async fn background_agent_runtime_tasks_can_run_in_parallel_and_persist_replies( design_runtime.last_response.as_deref(), Some("策划后台任务完成:先收敛核心循环。") ); - let art_runtime_result = - read_game_creator_agent_runtime_at(&root, "art-director").expect("art runtime result"); - let design_runtime_result = read_game_creator_agent_runtime_at(&root, "design-director") - .expect("design runtime result"); assert!(art_runtime_result .recent_tasks .iter() @@ -46171,9 +46159,6 @@ async fn background_agent_runtime_tasks_can_run_in_parallel_and_persist_replies( "assistant conversation must persist before Runtime completes for {agent_id}" ); } - drop(wait_to_acquire_agent_runtime_lock(&root, "art-director")); - drop(wait_to_acquire_agent_runtime_lock(&root, "design-director")); - fs::remove_dir_all(root).ok(); } diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index c35e017d5..28286a2bd 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -35,7 +35,7 @@ - 现象:isolated child 已显示 idle,单次 all-join reconcile 却偶发返回空列表;或者 Runtime 已显示 completed / failed,Goal、conversation、Agent DB 审计和 per-Agent lock 仍未完成,完整 Rust suite 里出现低概率失败,单独重跑通常通过。 - 原因:Runtime state、Goal sidecar、终态 result、conversation、审计记录、handoff 清理和执行 lane 释放不是同一个原子观测点;测试只等待 idle / failed 会在同一后台 drain 的 durable 收尾前抢先断言。 - 处理:产品协议仍以 durable terminal result 和 join readiness 为准。测试在有界时限内等待业务目标终态;需要断言同一 drain 的后续副作用时,同时以 per-Agent runtime task lock 释放为 fence,命中后重新读取投影。join 场景继续重复调用幂等 reconcile,直到取得唯一 join 或超时;不得靠固定长 sleep,也不能因为第一次为空就把协议改成吞掉未完成 child。 -- 验证:`isolated_agents_with_same_template_run_independently_and_join_once` 最多执行 100 次、每次间隔 20ms 的 reconcile,并继续断言只有一个 all-join 和一次父唤醒;Goal、loop-budget、finalization 与 Supervisor reconciliation 测试必须在目标 status / phase 与 Agent lane 同时收束后再读取最终副作用。 +- 验证:`isolated_agents_with_same_template_run_independently_and_join_once` 最多执行 100 次、每次间隔 20ms 的 reconcile,并继续断言只有一个 all-join 和一次父唤醒;Goal、loop-budget、finalization 与 Supervisor reconciliation 测试必须在目标 status / phase 与 Agent lane 同时收束后再读取最终副作用。`background_agent_runtime_marks_response_plan_step_failed_when_final_reply_fails` 和 `background_agent_runtime_tasks_can_run_in_parallel_and_persist_replies` 同样必须经过该 fence 后再断言 `turn.failed` 或 `agent.runtime.completed` 审计。 - 关联:`apps/ai-game-creator-shell/src-tauri/src/tests.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent.rs`。 ## CI root 环境不能用文件只读权限注入写失败