From bc4aea627f4d145725aba6cd79030a887981e20f Mon Sep 17 00:00:00 2001 From: Linghong Date: Mon, 31 Aug 2026 12:50:27 +0000 Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E7=90=86=E7=AD=96=E5=88=92=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E6=97=B6=E6=97=A0=E9=94=81=E5=8C=85=E8=A3=85=E5=85=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除无锁 tool-plan request builder wrapper 删除 approval pending 与 submit input parser wrapper 测试改为显式持锁调用 locked 实现及底层严格解析器 --- .../provider_request_builders.rs | 154 ++++++++++-------- .../runtime_protocol/planning_approval.rs | 17 -- .../runtime_protocol/planning_storage.rs | 35 ++-- .../agent/runtime_protocol/planning_submit.rs | 36 ++-- 4 files changed, 117 insertions(+), 125 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_request_builders.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_request_builders.rs index 030b89231..51e452b0f 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_request_builders.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/provider_request_builders.rs @@ -715,36 +715,6 @@ fn build_game_creator_agent_background_tool_plan_request_at( )) } -pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request( - root: &Path, - agent_id: &str, - session_id: &str, - run_id: &str, - task: &str, - observations: &[AgentRuntimeToolObservation], - loop_index: usize, -) -> Result< - ( - GameCreatorLlmConfig, - String, - LlmRunRequest, - String, - AgentRuntimeToolPlanRequestSnapshot, - ), - String, -> { - build_game_creator_agent_background_tool_plan_request_at( - root, - None, - agent_id, - session_id, - run_id, - task, - observations, - loop_index, - ) -} - pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request_locked( root: &Path, project_lock: &ProjectWriteLock, @@ -972,17 +942,20 @@ mod tests { use crate::{update_manifest_task_status_at, GameCreationAppTaskStatus}; 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, + build_game_creator_agent_background_tool_plan_request_locked, 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, + provider_command_start_contract, + render_relaxed_autonomous_manifest_ready_task_background_prompt, 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_COMPLETION_BLOCKER_TOOL_PLAN_PROTOCOL, AGENT_RUNTIME_PLAN_AUTONOMOUS_PROFILE_UNSUPPORTED_KIND, @@ -996,6 +969,40 @@ mod tests { RUNTIME_PROMPT_SUPERVISOR_CHAT_COMPOSITION, }; + fn build_game_creator_agent_background_tool_plan_request_for_test( + root: &std::path::Path, + agent_id: &str, + session_id: &str, + run_id: &str, + task: &str, + observations: &[AgentRuntimeToolObservation], + loop_index: usize, + ) -> Result< + ( + GameCreatorLlmConfig, + String, + platform_llm::LlmRunRequest, + String, + AgentRuntimeToolPlanRequestSnapshot, + ), + String, + > { + let lock = acquire_game_creator_agent_provider_plan_project_write_lock_with_wait( + root, + "test.provider_request.build.tool_plan", + )?; + build_game_creator_agent_background_tool_plan_request_locked( + root, + &lock, + agent_id, + session_id, + run_id, + task, + observations, + loop_index, + ) + } + fn native_input_required_fields( request: &platform_llm::LlmRunRequest, tool: &str, @@ -1066,7 +1073,7 @@ mod tests { summary: "结构化计划更新被 Runtime 拒绝".to_string(), detail: Some("计划状态回退".to_string()), }; - let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request( + let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request_for_test( &root, &state.agent_id, &state.session_id, @@ -1155,16 +1162,17 @@ mod tests { // Relaxed orchestration does not convert an idle planning counter into // a tool-removal gate; the Provider remains free to choose its next // action. - let (_, _, baseline, _, _) = build_game_creator_agent_background_tool_plan_request( - &root, - &state.agent_id, - &state.session_id, - &state.run_id, - &state.current_task, - &[], - 1, - ) - .expect("build baseline request"); + let (_, _, baseline, _, _) = + build_game_creator_agent_background_tool_plan_request_for_test( + &root, + &state.agent_id, + &state.session_id, + &state.run_id, + &state.current_task, + &[], + 1, + ) + .expect("build baseline request"); assert!(baseline .function_tools .iter() @@ -1175,7 +1183,7 @@ mod tests { crate::agent::write_game_creator_agent_runtime_state(&root, &idle_state) .expect("persist idle rounds"); - let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request( + let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request_for_test( &root, &state.agent_id, &state.session_id, @@ -1256,7 +1264,7 @@ mod tests { vec!["交付当前 manifest task".to_string()], ) .expect("start autonomous ready child task"); - let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request( + let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request_for_test( &root, agent_id, &state.session_id, @@ -1506,7 +1514,7 @@ mod tests { vec!["冻结 Goal Contract".to_string()], ) .expect("start trusted root"); - let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request( + let (_, _, request, _, _) = build_game_creator_agent_background_tool_plan_request_for_test( &root, &state.agent_id, &state.session_id, @@ -1566,7 +1574,7 @@ mod tests { ) .expect("start plan root"); - let (_, _, first, _, _) = build_game_creator_agent_background_tool_plan_request( + let (_, _, first, _, _) = build_game_creator_agent_background_tool_plan_request_for_test( &root, &state.agent_id, &state.session_id, @@ -1633,7 +1641,7 @@ mod tests { }, ) .expect("freeze plan contract"); - let (_, _, later, _, _) = build_game_creator_agent_background_tool_plan_request( + let (_, _, later, _, _) = build_game_creator_agent_background_tool_plan_request_for_test( &root, &state.agent_id, &state.session_id, @@ -1719,7 +1727,7 @@ mod tests { ) .expect("start supervisor runtime state"); let (_, _, supervisor_request, _, _) = - build_game_creator_agent_background_tool_plan_request( + build_game_creator_agent_background_tool_plan_request_for_test( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, &supervisor_state.session_id, @@ -1865,16 +1873,17 @@ mod tests { vec!["核对普通说明".to_string()], ) .expect("start ordinary runtime state"); - let (_, _, ordinary_request, _, _) = build_game_creator_agent_background_tool_plan_request( - &root, - "code-prototype", - &ordinary_state.session_id, - &ordinary_state.run_id, - &ordinary_state.current_task, - &[], - 0, - ) - .expect("build ordinary planning request"); + let (_, _, ordinary_request, _, _) = + build_game_creator_agent_background_tool_plan_request_for_test( + &root, + "code-prototype", + &ordinary_state.session_id, + &ordinary_state.run_id, + &ordinary_state.current_task, + &[], + 0, + ) + .expect("build ordinary planning request"); assert!(ordinary_request.messages[0] .content .contains("你正在使用 Genarrative AI 游戏创作多智能体 Runtime")); @@ -1989,16 +1998,17 @@ mod tests { vec!["读取需求并准备澄清".to_string()], ) .expect("start planning child"); - let (_, _, planning_request, _, _) = build_game_creator_agent_background_tool_plan_request( - &root, - GAME_CREATOR_PROJECT_PLANNING_AGENT_ID, - &planning_state.session_id, - &planning_state.run_id, - &planning_state.current_task, - &[], - 0, - ) - .expect("build planning request"); + let (_, _, planning_request, _, _) = + build_game_creator_agent_background_tool_plan_request_for_test( + &root, + GAME_CREATOR_PROJECT_PLANNING_AGENT_ID, + &planning_state.session_id, + &planning_state.run_id, + &planning_state.current_task, + &[], + 0, + ) + .expect("build planning request"); let planning_system_prompt = &planning_request.messages[0].content; let planning_brief_marker = "你是“立项策划 Agent”(`agentId=project-planning`)"; assert!(planning_system_prompt.contains(planning_brief_marker)); @@ -2074,7 +2084,7 @@ mod tests { ) .expect("start supervisor"); let (_, _, supervisor_request, _, _) = - build_game_creator_agent_background_tool_plan_request( + build_game_creator_agent_background_tool_plan_request_for_test( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, &supervisor_state.session_id, @@ -2183,7 +2193,7 @@ mod tests { }, ]; let (_, _, request, _, request_snapshot) = - build_game_creator_agent_background_tool_plan_request( + build_game_creator_agent_background_tool_plan_request_for_test( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, &state.session_id, @@ -2209,7 +2219,7 @@ mod tests { ), }); let (_, _, _, _, settled_request_snapshot) = - build_game_creator_agent_background_tool_plan_request( + build_game_creator_agent_background_tool_plan_request_for_test( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, &state.session_id, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_approval.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_approval.rs index 691ddb02d..bd7fac003 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_approval.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_approval.rs @@ -216,23 +216,6 @@ pub(crate) fn pending_matches_receipt( /// Construct the independent planning pending projection after an external /// acceptance gate has succeeded. M1C-1 does not decide whether the gate /// passed; the caller must supply that fact and the exact GDD identity. -pub(crate) fn create_plan_gdd_approval_pending_at( - root: &Path, - gdd: &PlanGddV1, -) -> Result<(), PlanningStorageError> { - if !crate::config::game_creator_planning_capability_enabled() - .map_err(|error| approval_error("PLAN_CAPABILITY_DISABLED", error))? - { - return Err(approval_error( - "PLAN_CAPABILITY_DISABLED", - "立项策划能力当前已停用", - )); - } - let _lock = acquire_project_write_lock(root, "planning.approval-pending.create") - .map_err(|error| approval_error("PLAN_DURABILITY_FAILED", error))?; - create_plan_gdd_approval_pending_locked(root, gdd) -} - pub(crate) fn create_plan_gdd_approval_pending_locked( root: &Path, gdd: &PlanGddV1, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_storage.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_storage.rs index fba95f5d6..5fd0646c6 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_storage.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_storage.rs @@ -2712,18 +2712,6 @@ pub(crate) fn canonical_plan_submit_gdd_input_bytes( Ok(bytes) } -pub(crate) fn parse_plan_submit_gdd_input_bytes( - bytes: &[u8], -) -> Result { - let value = parse_strict_canonical::( - bytes, - "plan.submit_gdd input", - PLAN_GDD_MAX_BYTES, - )?; - validate_plan_submit_gdd_input(&value)?; - Ok(value) -} - pub(crate) fn validate_plan_gdd_chain(values: &[PlanGddV1]) -> Result<(), PlanningStorageError> { if values.len() > PLAN_MAX_VERSIONS as usize { return Err(PlanningStorageError::new( @@ -5067,22 +5055,23 @@ mod tests { fn submit_input_has_strict_canonical_parser_and_runtime_field_boundary() { let value = golden_submit_input(); let bytes = canonical_plan_submit_gdd_input_bytes(&value).expect("submit input bytes"); - assert_eq!( - parse_plan_submit_gdd_input_bytes(&bytes).expect("parse input"), - value - ); + let parse = |bytes: &[u8]| -> Result { + let value = parse_strict_canonical::( + bytes, + "plan.submit_gdd input", + PLAN_GDD_MAX_BYTES, + )?; + validate_plan_submit_gdd_input(&value)?; + Ok(value) + }; + assert_eq!(parse(&bytes).expect("parse input"), value); let mut newline = bytes.clone(); newline.push(b'\n'); - assert_eq!( - parse_plan_submit_gdd_input_bytes(&newline) - .unwrap_err() - .code(), - "PLAN_NON_CANONICAL_BYTES" - ); + assert_eq!(parse(&newline).unwrap_err().code(), "PLAN_NON_CANONICAL_BYTES"); let mut object = serde_json::from_slice::(&bytes).expect("input json"); object["projectId"] = serde_json::Value::String("forged-project".to_string()); let forged = serde_json::to_vec(&object).expect("forged input"); - assert!(parse_plan_submit_gdd_input_bytes(&forged).is_err()); + assert!(parse(&forged).is_err()); } #[test] diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_submit.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_submit.rs index dfb10dbc6..cfac42abc 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_submit.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_submit.rs @@ -1896,6 +1896,15 @@ mod tests { use std::fs; use std::path::PathBuf; + fn create_plan_gdd_approval_pending_for_test( + root: &std::path::Path, + gdd: &PlanGddV1, + ) -> Result<(), PlanningStorageError> { + let _lock = acquire_project_write_lock(root, "test.planning.approval-pending.create") + .map_err(|error| PlanningStorageError::new("PLAN_DURABILITY_FAILED", error))?; + create_plan_gdd_approval_pending_locked(root, gdd) + } + fn valid_input() -> PlanSubmitGddInputV1 { PlanSubmitGddInputV1 { schema_version: PLAN_SUBMIT_INPUT_SCHEMA.to_string(), @@ -3628,7 +3637,7 @@ mod tests { .expect("read submitted GDD") .pop() .expect("GDD exists"); - create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending"); + create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending"); let decision_input = approval_input( &gdd, "approve", @@ -3654,13 +3663,13 @@ mod tests { .pop() .expect("GDD exists"); - create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending"); + create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending"); let pending = read_plan_gdd_approval_pending_locked(&root) .expect("read approval pending") .expect("pending exists"); assert_eq!(pending.status, "awaiting_decision"); // Recreating the exact card is an idempotent replay. - create_plan_gdd_approval_pending_at(&root, &gdd).expect("replay approval pending"); + create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("replay approval pending"); let mut forged_next = gdd.clone(); forged_next.version = 2; @@ -3669,7 +3678,7 @@ mod tests { "gdd-approval-00000000-0000-4000-8000-000000000003".to_string(); forged_next.action_fingerprint = "4".repeat(64); forged_next.fingerprint = plan_gdd_fingerprint(&forged_next).expect("next fingerprint"); - let stale = create_plan_gdd_approval_pending_at(&root, &forged_next) + let stale = create_plan_gdd_approval_pending_for_test(&root, &forged_next) .expect_err("a non-latest GDD cannot receive an approval card"); assert_eq!(stale.code(), "PLAN_STALE_APPROVAL"); @@ -3684,7 +3693,7 @@ mod tests { ) .expect("commit approval receipt"); assert_eq!(decision.outcome, "committed"); - let after_receipt = create_plan_gdd_approval_pending_at(&root, &gdd) + let after_receipt = create_plan_gdd_approval_pending_for_test(&root, &gdd) .expect_err("a receipt must close awaiting_decision recreation"); assert_eq!(after_receipt.code(), "PLAN_STALE_APPROVAL"); cleanup_fixture(root); @@ -3698,7 +3707,7 @@ mod tests { .expect("read submitted GDD") .pop() .expect("GDD exists"); - create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending"); + create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending"); let task_path = game_creator_agent_runtime_task_path(&root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID); @@ -3738,7 +3747,7 @@ mod tests { .expect("read submitted GDD") .pop() .expect("GDD exists"); - create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending"); + create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending"); let _config_guard = crate::tests::write_test_local_config( r#"{"planning":{"capabilityEnabled":false}}"#.to_string(), ); @@ -3802,7 +3811,7 @@ mod tests { assert_eq!(blocker.status, "needs-reconciliation"); assert!(blocker.summary.contains("审批 pending")); - create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending"); + create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending"); let blocker = plan_gdd_completion_blocker_at_locked( &root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, @@ -3955,7 +3964,7 @@ mod tests { .expect("read submitted GDD") .pop() .expect("GDD exists"); - create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending"); + create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending"); decide_plan_gdd_at( &root, &approval_input( @@ -4089,7 +4098,7 @@ mod tests { #[test] fn a_revision_comment_reaches_the_supervisor_conversation_once() { let (root, gdd, _root_runtime) = acceptance_gate_fixture(true); - create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending"); + create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending"); decide_plan_gdd_at( &root, &approval_input( @@ -4143,7 +4152,7 @@ mod tests { .expect("read submitted GDD") .pop() .expect("GDD exists"); - create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending"); + create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending"); decide_plan_gdd_at( &root, &approval_input( @@ -4230,7 +4239,8 @@ mod tests { .expect("read submitted GDD") .pop() .expect("GDD exists"); - create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending"); + create_plan_gdd_approval_pending_for_test(&root, &gdd) + .expect("create approval pending"); let first_input = approval_input( &gdd, action, @@ -4461,7 +4471,7 @@ mod tests { .expect("read submitted GDD") .pop() .expect("GDD exists"); - create_plan_gdd_approval_pending_at(&root, &gdd).expect("create approval pending"); + create_plan_gdd_approval_pending_for_test(&root, &gdd).expect("create approval pending"); let decision_input = approval_input( &gdd, "approve",