From 2ec05ba85d883c3f622cfb43ab7a724956a1d86e Mon Sep 17 00:00:00 2001 From: AIGameCreator App Date: Wed, 22 Jul 2026 14:43:55 +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=E9=AA=8C=E8=AF=81=E6=94=B6=E6=95=9B=E4=B8=8E=E6=B5=8F?= =?UTF-8?q?=E8=A7=88=E5=99=A8=E5=8F=96=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 限制旧试玩失败下的重复委派并强制当前 revision 复验 收紧固定试玩控件唯一可见启用合同 为 Chrome 使用短临时目录并支持当前 run 截图别名 补齐确定性门禁与运行时决策记录 --- ...ent-runtime-deterministic-playable-e2e.mjs | 6 +- .../deterministic-lane-defense-provider.mjs | 14 +- .../src-tauri/src/agent/runtime_actions.rs | 193 ++++++- .../src-tauri/src/agent/runtime_driver.rs | 26 +- .../src-tauri/src/agent/runtime_protocol.rs | 69 ++- .../src-tauri/src/browser.rs | 46 +- .../src-tauri/src/image_inspect.rs | 113 ++++- .../planning_strategy/autonomous_build.rs | 472 ++++++++++++++++++ .../shared-memory/decision-log.md | 15 + docs/project-memory/shared-memory/pitfalls.md | 22 + ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 6 +- 11 files changed, 952 insertions(+), 30 deletions(-) diff --git a/apps/ai-game-creator-shell/scripts/agent-runtime-deterministic-playable-e2e.mjs b/apps/ai-game-creator-shell/scripts/agent-runtime-deterministic-playable-e2e.mjs index 6e611ff8a..da1b7484c 100644 --- a/apps/ai-game-creator-shell/scripts/agent-runtime-deterministic-playable-e2e.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-runtime-deterministic-playable-e2e.mjs @@ -236,6 +236,11 @@ async function runSelfTest() { route('code-prototype', 'repair-code-run'); route('code-prototype', 'repair-code-run'); route('code-prototype', 'repair-code-run', allTools, builderPlan); + assert( + responseFunctionNames(route('project-supervisor', 'parent-run'))[0] === + 'runtime_tool_agent_run_status', + 'self-test-repair-claim-before-verification-invalid', + ); assert( responseFunctionNames(route('project-supervisor', 'parent-run')).join( ',', @@ -243,7 +248,6 @@ async function runSelfTest() { 'self-test-repair-verification-batch-invalid', ); route('project-supervisor', 'parent-run'); - route('project-supervisor', 'parent-run'); const stats = router.getStats(); assert(expectedProviderStats(stats), 'self-test-provider-stats-invalid'); diff --git a/apps/ai-game-creator-shell/scripts/deterministic-lane-defense-provider.mjs b/apps/ai-game-creator-shell/scripts/deterministic-lane-defense-provider.mjs index d2f5712dc..06f911f18 100644 --- a/apps/ai-game-creator-shell/scripts/deterministic-lane-defense-provider.mjs +++ b/apps/ai-game-creator-shell/scripts/deterministic-lane-defense-provider.mjs @@ -357,19 +357,19 @@ export function createDeterministicLaneDefenseRouter({ repairPatchCall('根据浏览器失败诊断直接修复不可见 canvas'), ]); case 'verify-repair': - parentStage = 'claim-repair'; + parentStage = 'verify-repair-after-claim'; + stats.runStatusCount += 1; + return callsResponse(tools, [ + runStatusCall('先认领返工后的专业交付,再验证最新 revision'), + ]); + case 'verify-repair-after-claim': + parentStage = 'respond'; stats.staticSmokeCount += 1; stats.previewValidationCount += 1; return callsResponse(tools, [ staticSmokeCall('验证返工后的当前 revision'), previewCall('重跑完整真实试玩和双视口检查'), ]); - case 'claim-repair': - parentStage = 'respond'; - stats.runStatusCount += 1; - return callsResponse(tools, [ - runStatusCall('认领通过复验的专业返工回执'), - ]); case 'respond': parentStage = 'done'; return callsResponse(tools, [ diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions.rs index c5ecf594a..6ad293d45 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions.rs @@ -5954,8 +5954,10 @@ pub(super) async fn request_game_creator_agent_background_tool_plan_at( } else { false }; - match validate_agent_runtime_autonomous_plan_liveness( + match validate_agent_runtime_autonomous_plan_liveness_at( + root, agent_id, + run_id, loop_index, project_revision, &verification_gate, @@ -6181,6 +6183,18 @@ pub(super) async fn request_game_creator_agent_background_tool_plan_at( AGENT_RUNTIME_AUTONOMOUS_REVERIFY_AFTER_MUTATION_LIVENESS_ERROR_PREFIX, ) && !request.function_tools.is_empty(); + let force_autonomous_supervisor_delivery_convergence = run_profile + == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD + && protocol_error.starts_with( + AGENT_RUNTIME_AUTONOMOUS_SUPERVISOR_DELIVERY_CONVERGENCE_LIVENESS_ERROR_PREFIX, + ) + && !request.function_tools.is_empty(); + let force_autonomous_preview_after_static = run_profile + == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD + && protocol_error.starts_with( + AGENT_RUNTIME_AUTONOMOUS_PREVIEW_AFTER_STATIC_LIVENESS_ERROR_PREFIX, + ) + && !request.function_tools.is_empty(); let force_autonomous_verified_delivery = run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD && protocol_error.starts_with( @@ -6219,6 +6233,8 @@ pub(super) async fn request_game_creator_agent_background_tool_plan_at( || force_autonomous_failed_playtest || force_autonomous_pending_verification || force_autonomous_reverify_after_mutation + || force_autonomous_supervisor_delivery_convergence + || force_autonomous_preview_after_static || force_autonomous_verified_delivery || force_autonomous_truncated_scaffold || force_autonomous_pre_mutation @@ -6236,6 +6252,20 @@ pub(super) async fn request_game_creator_agent_background_tool_plan_at( request.messages.push(LlmMessage::user(format!( "上一条输出不符合工具计划协议:{protocol_error}\n本次修复的原生工具目录只保留 update_agent_plan 与 respond_to_user。必须在同一响应先调用 update_agent_plan,保留原步骤标题并把已经真实完成的全部步骤标为 completed,再调用 respond_to_user 交付刚才已经形成的结论。不得只调用其中一个函数,不得新增步骤、继续读取、修改项目或解释。" ))); + } else if force_autonomous_supervisor_delivery_convergence { + restrict_agent_runtime_autonomous_supervisor_delivery_convergence_repair_tools( + &mut request, + )?; + request.messages.push(LlmMessage::user(format!( + "上一条输出不符合工具计划协议:{protocol_error}\n当前父 run 已有 ready 未认领回执或 3 个 active delivery,本次修复的原生工具目录只保留 agent.run_status。必须立即以 agentId=null、scope=all、delegationId=null 查询状态并原子认领 readyDelegateReceipts;不得创建第四次 agent.delegate、更新计划、读取、搜索、修改项目、重复验证或 respond_to_user。认领完成后再依据最新 project revision 重新规划验证。不要解释,不要 markdown,不要代码围栏。" + ))); + } else if force_autonomous_preview_after_static { + restrict_agent_runtime_autonomous_preview_after_static_repair_tools( + &mut request, + )?; + request.messages.push(LlmMessage::user(format!( + "上一条输出不符合工具计划协议:{protocol_error}\n当前 revision 已通过 game.static_smoke,本次修复的原生工具目录只保留 preview.validate。必须立即对 desktop 与 mobile 执行当前完成合同绑定的真实浏览器试玩;不得继续委派、更新计划、读取、搜索、查询状态、修改项目或 respond_to_user。不要解释,不要 markdown,不要代码围栏。" + ))); } else if force_autonomous_delegated_playtest_repair { restrict_agent_runtime_autonomous_delegated_playtest_repair_tools( &mut request, @@ -9443,7 +9473,111 @@ pub(super) fn validate_agent_runtime_autonomous_response_plan_completion( )) } -pub(super) fn validate_agent_runtime_autonomous_plan_liveness( +const AGENT_RUNTIME_AUTONOMOUS_SUPERVISOR_DELIVERY_CONVERGENCE_LIVENESS_ERROR_PREFIX: &str = + "自主构建 Project Supervisor 必须先收束已有专业 Agent 委派"; +const AGENT_RUNTIME_AUTONOMOUS_PREVIEW_AFTER_STATIC_LIVENESS_ERROR_PREFIX: &str = + "自主构建 Project Supervisor 必须试玩当前静态验证 revision"; + +fn is_agent_runtime_autonomous_supervisor_delivery_convergence_plan( + plan: &AgentRuntimeToolPlan, +) -> bool { + matches!(plan.actions.as_slice(), [action] + if action.tool.trim() == "agent.run_status" + && action.input.get("agentId").is_none_or(serde_json::Value::is_null) + && action.input.get("agent_id").is_none_or(serde_json::Value::is_null) + && action.input.get("scope").and_then(serde_json::Value::as_str) == Some("all") + && action.input.get("delegationId").is_none_or(serde_json::Value::is_null) + && action.input.get("delegation_id").is_none_or(serde_json::Value::is_null)) +} + +fn is_agent_runtime_autonomous_preview_validation_plan(plan: &AgentRuntimeToolPlan) -> bool { + matches!(plan.actions.as_slice(), [action] if action.tool.trim() == "preview.validate") +} + +pub(crate) fn refresh_agent_runtime_autonomous_convergence_snapshot_after_provider_at( + root: &Path, + agent_id: &str, + run_profile: &str, + plan: &AgentRuntimeToolPlan, + project_revision_before: &AgentRuntimeProjectRevision, + repository_context_fingerprint_before: &str, +) -> Result<(AgentRuntimeProjectRevision, String), String> { + if agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID + || run_profile != AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD + || !is_agent_runtime_autonomous_supervisor_delivery_convergence_plan(plan) + { + return Ok(( + project_revision_before.clone(), + repository_context_fingerprint_before.to_string(), + )); + } + + let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait( + root, + "runtime.provider_request.rebind.convergence", + )?; + Ok(( + read_game_creator_agent_runtime_project_revision(root)?, + build_repository_startup_context_at(root)?.fingerprint, + )) +} + +fn validate_agent_runtime_autonomous_plan_liveness_at( + root: &Path, + agent_id: &str, + run_id: &str, + loop_index: usize, + project_revision: u64, + verification_gate: &AgentRuntimeVerificationGate, + observations: &[AgentRuntimeToolObservation], + plan: &AgentRuntimeToolPlan, + supervisor_requires_delegated_repair: bool, +) -> Result<(), String> { + if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID + && supervisor_requires_delegated_repair + && verification_gate + .failed_playtest_revision + .is_some_and(|failed_revision| failed_revision <= project_revision) + { + let barrier = static_delegate_completion_barrier_at(root, agent_id, run_id)?; + let active_delegations = active_static_delegate_delivery_count_at(root, agent_id, run_id)?; + let must_claim_or_wait = barrier.ready_unclaimed_count > 0 || active_delegations >= 3; + if must_claim_or_wait { + if is_agent_runtime_autonomous_supervisor_delivery_convergence_plan(plan) { + return Ok(()); + } + return Err(format!( + "{AGENT_RUNTIME_AUTONOMOUS_SUPERVISOR_DELIVERY_CONVERGENCE_LIVENESS_ERROR_PREFIX};当前父 run 的 activeDelegations={active_delegations}、waitingDelegations={}、readyUnclaimedReceipts={}。必须先只调用 agent.run_status(agentId=null、scope=all、delegationId=null)认领 ready delivery 或等待已有委派推进;不得创建第四次 agent.delegate。认领后若项目 revision 已推进,再验证当前 revision", + barrier.waiting_count, + barrier.ready_unclaimed_count, + )); + } + } + + let has_current_playtest_receipt = if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID { + let contract = read_autonomous_completion_contract(root, agent_id, run_id)?; + match contract.as_ref() { + Some(contract) => read_autonomous_playtest_receipt(root, contract)? + .is_some_and(|receipt| receipt.revision == project_revision), + None => false, + } + } else { + false + }; + + validate_agent_runtime_autonomous_plan_liveness( + agent_id, + loop_index, + project_revision, + verification_gate, + observations, + plan, + supervisor_requires_delegated_repair, + has_current_playtest_receipt, + ) +} + +pub(crate) fn validate_agent_runtime_autonomous_plan_liveness( agent_id: &str, loop_index: usize, project_revision: u64, @@ -9451,6 +9585,7 @@ pub(super) fn validate_agent_runtime_autonomous_plan_liveness( observations: &[AgentRuntimeToolObservation], plan: &AgentRuntimeToolPlan, supervisor_requires_delegated_repair: bool, + has_current_playtest_receipt: bool, ) -> Result<(), String> { let has_mutation = plan .actions @@ -9493,6 +9628,20 @@ pub(super) fn validate_agent_runtime_autonomous_plan_liveness( )); } } + if !has_current_playtest_receipt + && verification_gate.failed_playtest_revision.is_none() + && verification_gate.verified_revision == Some(project_revision) + && verification_gate.last_verification_tool.as_deref() == Some("game.static_smoke") + && verification_gate.last_verification_status.as_deref() + == Some(AGENT_RUNTIME_VERIFICATION_STATUS_PASSED) + { + if is_agent_runtime_autonomous_preview_validation_plan(plan) { + return Ok(()); + } + return Err(format!( + "{AGENT_RUNTIME_AUTONOMOUS_PREVIEW_AFTER_STATIC_LIVENESS_ERROR_PREFIX};当前 revision {project_revision} 已通过 game.static_smoke,下一步必须只调用 preview.validate 取得当前 revision 的桌面与移动真实试玩凭证;不得继续委派、更新计划、读取、搜索、查询状态、修改项目或返回最终回复" + )); + } let Some(latest_playtest_index) = observations .iter() .rposition(|observation| observation.tool == "preview.validate") @@ -9859,6 +10008,46 @@ pub(super) fn restrict_agent_runtime_autonomous_failed_playtest_repair_tools( Ok(()) } +fn restrict_agent_runtime_autonomous_supervisor_delivery_convergence_repair_tools( + request: &mut LlmRunRequest, +) -> Result<(), String> { + let run_status_function = native_runtime_function_name("agent.run_status") + .ok_or_else(|| "无法生成自主试玩委派收敛工具名".to_string())?; + request + .function_tools + .retain(|tool| tool.name == run_status_function); + if request.function_tools.len() != 1 { + return Err("自主试玩委派收敛工具目录缺少 agent.run_status".to_string()); + } + request.max_output_tokens = Some( + request + .max_output_tokens + .unwrap_or(AGENT_RUNTIME_AUTONOMOUS_FORCED_ACTION_MAX_OUTPUT_TOKENS) + .min(AGENT_RUNTIME_AUTONOMOUS_FORCED_ACTION_MAX_OUTPUT_TOKENS), + ); + Ok(()) +} + +fn restrict_agent_runtime_autonomous_preview_after_static_repair_tools( + request: &mut LlmRunRequest, +) -> Result<(), String> { + let preview_function = native_runtime_function_name("preview.validate") + .ok_or_else(|| "无法生成自主当前 revision 试玩工具名".to_string())?; + request + .function_tools + .retain(|tool| tool.name == preview_function); + if request.function_tools.len() != 1 { + return Err("自主当前 revision 试玩工具目录缺少 preview.validate".to_string()); + } + request.max_output_tokens = Some( + request + .max_output_tokens + .unwrap_or(AGENT_RUNTIME_AUTONOMOUS_FORCED_ACTION_MAX_OUTPUT_TOKENS) + .min(AGENT_RUNTIME_AUTONOMOUS_FORCED_ACTION_MAX_OUTPUT_TOKENS), + ); + Ok(()) +} + pub(super) fn restrict_agent_runtime_autonomous_delegated_playtest_repair_tools( request: &mut LlmRunRequest, ) -> Result<(), String> { diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs index de57a3e53..e582aebe8 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs @@ -7613,8 +7613,8 @@ pub(super) async fn run_game_creator_agent_background_task_pass_with_context( } else { None }; - let planning_request_revision: AgentRuntimeProjectRevision; - let planning_repository_context_fingerprint: String; + let mut planning_request_revision: AgentRuntimeProjectRevision; + let mut planning_repository_context_fingerprint: String; let action_start_index: usize; if let Some(batch) = resumed_provider_batch.as_ref() { let Some(first_pending) = batch.actions.first() else { @@ -7988,6 +7988,28 @@ pub(super) async fn run_game_creator_agent_background_task_pass_with_context( planning_repository_context_fingerprint = requested_plan.repository_context_fingerprint; let planning_mcp_catalog_fingerprint = requested_plan.mcp_catalog_fingerprint; plan = requested_plan.plan; + match refresh_agent_runtime_autonomous_convergence_snapshot_after_provider_at( + &root, + &agent_id, + &runtime.run_profile, + &plan, + &planning_request_revision, + &planning_repository_context_fingerprint, + ) { + Ok((revision, repository_context_fingerprint)) => { + planning_request_revision = revision; + planning_repository_context_fingerprint = repository_context_fingerprint; + } + Err(error) => { + return fail_game_creator_agent_background_context_at( + &root, + &agent_id, + &session_id, + runtime, + &format!("Provider 返回后刷新自主委派收敛快照失败:{error}"), + ); + } + } if runtime.run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD && !plan.response.trim().is_empty() { diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol.rs index cc1fbc976..21556cdfc 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol.rs @@ -5917,10 +5917,19 @@ pub(super) fn autonomous_playtest_contract_prompt( ) -> &'static str { match scenario { BrowserPlaytestScenario::GenericV1 => { - "完成合同要求 generic-v1 交互试玩。game/index.html 必须持续更新