From 77983c05edc20ac5eb63db9995d19593908e1bd2 Mon Sep 17 00:00:00 2001 From: kdletters Date: Wed, 12 Aug 2026 19:48:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Native=20shell=20=E5=A7=94?= =?UTF-8?q?=E6=B4=BE=E5=8D=8F=E8=AE=AE=E5=9B=9E=E5=BD=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 兼容未携带澄清字段的既有委派工具调用 同步原生委派 schema 契约测试 调整试玩失败门禁优先级并补充反向回归 记录委派协议演进与门禁优先级约束 --- .../runtime_actions/autonomous_policy.rs | 67 +++++++++++++++++-- .../src-tauri/src/agent_native_tools.rs | 39 ++++++++--- .../src-tauri/src/tests/provider.rs | 5 +- docs/project-memory/shared-memory/pitfalls.md | 2 + 4 files changed, 98 insertions(+), 15 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/autonomous_policy.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/autonomous_policy.rs index 7727f2188..4e6d8528d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/autonomous_policy.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/autonomous_policy.rs @@ -702,16 +702,25 @@ pub(crate) fn validate_agent_runtime_autonomous_plan_liveness( .actions .iter() .any(|action| action.tool.trim() == "agent.route_manifest"); + let latest_playtest_index = observations + .iter() + .rposition(|observation| observation.tool == "preview.validate"); + let latest_playtest_is_failed = latest_playtest_index.is_some_and(|index| { + observations[index].status == "failed" + && observations[index].summary == "浏览器验证未通过,请根据诊断修复后重试" + }); // A Supervisor without a mutation must still hit the same first-mutation - // liveness gate as every other autonomous Agent. Previously the presence - // of the Supervisor role alone could bypass this check. + // liveness gate as every other autonomous Agent. A concrete failed + // playtest is more specific and must reach its repair gate below. if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID && loop_index > AGENT_RUNTIME_AUTONOMOUS_PRE_MUTATION_LOOP_LIMIT && verification_gate.mutation_revision.is_none() + && verification_gate.failed_playtest_revision.is_none() && !has_mutation && !has_code_asset_route && plan.response.trim().is_empty() && !has_specialist_delegation + && !latest_playtest_is_failed { return Err(format!( "{AGENT_RUNTIME_AUTONOMOUS_LIVENESS_ERROR_PREFIX};Supervisor 尚未提交首次项目 mutation 或有效协作动作,禁止继续只规划、读取、验证或空转" @@ -758,10 +767,7 @@ pub(crate) fn validate_agent_runtime_autonomous_plan_liveness( "{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") - else { + let Some(latest_playtest_index) = latest_playtest_index else { return Ok(()); }; let latest_playtest = &observations[latest_playtest_index]; @@ -1961,4 +1967,53 @@ mod tests { .expect_err("Supervisor without a playtest must not bypass pre-mutation liveness"); assert!(error.starts_with(AGENT_RUNTIME_AUTONOMOUS_LIVENESS_ERROR_PREFIX)); } + + #[test] + fn autonomous_supervisor_latest_successful_playtest_does_not_bypass_pre_mutation_gate() { + let verification_gate = AgentRuntimeVerificationGate { + schema_version: "test".to_string(), + project_id: "test".to_string(), + agent_id: GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID.to_string(), + run_id: "supervisor-latest-playtest".to_string(), + requires_verification: false, + mutation_revision: None, + verified_revision: None, + last_mutation_tool: None, + last_verification_tool: None, + last_verification_status: None, + static_smoke_verified_revision: None, + failed_playtest_revision: None, + updated_at: 0, + }; + let observations = [ + AgentRuntimeToolObservation { + tool: "preview.validate".to_string(), + status: "failed".to_string(), + summary: "浏览器验证未通过,请根据诊断修复后重试".to_string(), + detail: None, + }, + AgentRuntimeToolObservation { + tool: "preview.validate".to_string(), + status: "ok".to_string(), + summary: "浏览器验证通过".to_string(), + detail: None, + }, + ]; + let plan = AgentRuntimeToolPlan { + thinking_summary: "继续只读规划".to_string(), + ..AgentRuntimeToolPlan::default() + }; + let error = validate_agent_runtime_autonomous_plan_liveness( + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + AGENT_RUNTIME_AUTONOMOUS_PRE_MUTATION_LOOP_LIMIT + 1, + 0, + &verification_gate, + &observations, + &plan, + false, + false, + ) + .expect_err("latest successful playtest must not retain an earlier failure exemption"); + assert!(error.starts_with(AGENT_RUNTIME_AUTONOMOUS_LIVENESS_ERROR_PREFIX)); + } } diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs b/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs index 906423910..6a9e44e4e 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs @@ -539,14 +539,25 @@ fn validate_native_agent_delegate_input( true, )?; validate_native_delegate_string(object.get("runId"), "runId", 160, true)?; - validate_native_delegate_string( - object.get("continuationOfDelegationId"), - "continuationOfDelegationId", - 160, - true, - )?; - validate_native_delegate_string(object.get("questionsSha256"), "questionsSha256", 64, true)?; - validate_native_delegate_string(object.get("answersSha256"), "answersSha256", 64, true)?; + if object.contains_key("continuationOfDelegationId") { + validate_native_delegate_string( + object.get("continuationOfDelegationId"), + "continuationOfDelegationId", + 160, + true, + )?; + } + if object.contains_key("questionsSha256") { + validate_native_delegate_string( + object.get("questionsSha256"), + "questionsSha256", + 64, + true, + )?; + } + if object.contains_key("answersSha256") { + validate_native_delegate_string(object.get("answersSha256"), "answersSha256", 64, true)?; + } if object .get("repairOfDelegationId") .is_some_and(Value::is_string) @@ -1589,6 +1600,18 @@ mod tests { .expect("complete clarification continuation binding"); } + #[test] + fn native_agent_delegate_accepts_legacy_input_without_clarification_fields() { + let mut input = valid_delegate_input(Value::Null, Value::Null); + let object = input.as_object_mut().expect("delegate input object"); + object.remove("continuationOfDelegationId"); + object.remove("questionsSha256"); + object.remove("answersSha256"); + + validate_native_agent_delegate_input(&input) + .expect("legacy delegate input without clarification fields"); + } + #[test] fn native_agent_delegate_rejects_partial_or_invalid_clarification_binding() { let mut partial = valid_delegate_input(json!("delegation-id"), Value::Null); diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/provider.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/provider.rs index d328906ab..e36d3c1ff 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/provider.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/provider.rs @@ -7723,7 +7723,10 @@ fn agent_native_function_catalog_exposes_each_runtime_tool_with_core_schemas() { "acceptanceCriteria", "expectedArtifacts", "repairOfDelegationId", - "runId" + "runId", + "continuationOfDelegationId", + "questionsSha256", + "answersSha256" ]) ); assert_eq!( diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 3109bd61c..94be7941e 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -4740,6 +4740,8 @@ - 正确路径:child 返回短小的 `AGC_NEEDS_USER_INPUT_V1` envelope;Runtime 生成 `needs-user-input` delivery,父 Supervisor 认领后创建自己的 durable `user.input_request`。回答仍绑定原父 run,续建 child 由稳定 delegation identity 幂等控制。 - 验证:重复 wake / Runner 重启不得创建第二个用户输入 action;问题数量、字段长度、问题 SHA 和答案 SHA 不匹配时必须 fail-closed。child 直接请求用户输入仍应保持拒绝。 - 恢复加固:正常 completed child 的最终回复也必须进入 envelope 解析;回答后 pending 会被下一轮动作替换,因此 continuation 不能读取 current pending 作为证据,必须读取原 delivery 上的 durable request/answer 绑定。多个 child 各用一条用户请求逐一收束,禁止把不同 delivery 的问题和答案指纹拍平混用。 +- 协议演进:`agent.delegate` 的澄清 continuation 字段虽然在 strict schema 中是 required nullable,但 Runtime 解析器仍必须接受完全未携带这三个字段的既有调用;只允许三者全缺失、全 `null` 或全为合法字符串,部分出现、部分字符串和非法 SHA 均失败关闭。新增 schema 字段时要同步原生函数目录断言与旧调用回归,避免协议修复轮次打乱 Supervisor 协作计划。 +- 门禁优先级:已经存在真实 `preview.validate` 失败 observation 或 durable `failed_playtest_revision` 时,具体试玩修复与新 revision 重新验证门禁必须先于通用“首次 mutation”门禁;否则 Runtime 会把明确的试玩修复错误收窄成普通 pre-mutation repair,导致 Supervisor 无法选择正确的协作动作。 ## 无限画布延迟草稿与零位移不能制造新状态(2026-08-11)