From 535eef8b584dce3f652b28968420966f6bb2f689 Mon Sep 17 00:00:00 2001 From: Linghong Date: Sun, 23 Aug 2026 16:44:48 +0000 Subject: [PATCH] =?UTF-8?q?=E6=BE=84=E6=B8=85=E7=BB=AD=E8=B7=91=E7=9A=84?= =?UTF-8?q?=E4=B8=A4=E4=B8=AA=E6=8C=87=E7=BA=B9=E6=94=B9=E7=94=B1=20Runtim?= =?UTF-8?q?e=20=E8=A1=A5=E9=BD=90=EF=BC=8C=E4=B8=8D=E5=86=8D=E8=AE=A9=20Su?= =?UTF-8?q?pervisor=20=E6=89=8B=E6=8A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit agent.delegate 的澄清 continuation 要求 Supervisor 在输入里填 questionsSha256/answersSha256 两个 64 位十六进制串。实测这会打死整个 plan 根 run:Supervisor 先抄成上一轮的指纹(校验不一致),再抄成非 hex 格式,工具计划格式修复两次仍失败后整轮判 failed,用户已提交的两轮澄清 回答全部作废。 这两个值原 delivery 已经唯一确定:resolve 时本来就从 delivery 读出 权威值来算 continuation identity,输入侧那份只被拿来比对,不参与任何 计算。手抄 128 个十六进制字符没有信息增益,只增加失败面。 现在指纹可以整体省略,由 Runtime 从 repairOfDelegationId 指向的原 delivery 补齐;填了仍然逐字校验。锚点 continuationOfDelegationId 保持 必填。三个字段一律按 JSON null 等同缺省处理,与既有可空字段一致。 Runtime 注入的澄清任务正文同步改成明确告知不要自己填写指纹。 共享 helper 上加了等价断言:省略指纹推导出的 continuation identity 必须 与手填时逐字相同,所有澄清 continuation 用例顺带覆盖。 本机 planning_clarification_answer_prepared_recovery_releases_execution_ before_project_wait 在改动前后同样失败(同一条断言),是既有环境失败。 Co-Authored-By: Claude Opus 5 --- .../agent/runtime_driver/provider_recovery.rs | 2 +- .../src-tauri/src/agent_native_tools.rs | 75 ++++++++++++------- .../src-tauri/src/delegation.rs | 8 +- .../tests/collaboration/static_deliveries.rs | 12 +++ 4 files changed, 69 insertions(+), 28 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/provider_recovery.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/provider_recovery.rs index 085408e7a..321a6acfb 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/provider_recovery.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/provider_recovery.rs @@ -391,7 +391,7 @@ pub(crate) fn ensure_static_delegate_user_input_wait_at_locked( let question_binding = game_creator_agent_user_input_action_input_summary(&action.input) .unwrap_or_else(|| "questionsSha256=unavailable".to_string()); let task = format!( - "{AGENT_RUNTIME_DELEGATE_CLARIFICATION_TASK_PREFIX}{};{question_binding}。请回答以下问题;回答完成后只创建一次 agent.delegate continuation,并将 repairOfDelegationId 与 continuationOfDelegationId 指向该原 delegation,同时提交 questionsSha256/answersSha256。", + "{AGENT_RUNTIME_DELEGATE_CLARIFICATION_TASK_PREFIX}{};{question_binding}。请回答以下问题;回答完成后只创建一次 agent.delegate continuation,并将 repairOfDelegationId 与 continuationOfDelegationId 指向该原 delegation。questionsSha256/answersSha256 由 Runtime 从原 delivery 补齐,你不要自己填写。", delivery.delegation_id ); // Re-entry after a wake or restart may only reuse the exact request that 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 2acbe61e7..19612be70 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 @@ -785,28 +785,27 @@ fn validate_native_agent_delegate_input( "Agent 原生工具协议错误:agent.delegate 返工委派时 runId 必须为 JSON null", )); } - let continuation_fields = [ - "continuationOfDelegationId", - "questionsSha256", - "answersSha256", - ] - .iter() - .filter(|field| object.get(**field).is_some_and(Value::is_string)) - .count(); - let continuation_present = [ - "continuationOfDelegationId", - "questionsSha256", - "answersSha256", - ] - .iter() - .filter(|field| object.contains_key(**field)) - .count(); - if (continuation_present != 0 && continuation_present != 3) - || (continuation_fields != 0 && continuation_fields != 3) - { + // 澄清 continuation 的锚点是 continuationOfDelegationId;两个指纹可以整体省略, + // 由 Runtime 从原 delivery 取权威值补齐。省略是为了不让 Supervisor 手抄 128 个 + // 十六进制字符——抄错会打到硬失败,而抄对也不带来任何 Runtime 不知道的信息。 + // 三个字段一律按“JSON null 等同于缺省”处理,与本函数其余可空字段一致。 + let continuation_anchor = object + .get("continuationOfDelegationId") + .is_some_and(Value::is_string); + let digest_strings = ["questionsSha256", "answersSha256"] + .iter() + .filter(|field| object.get(**field).is_some_and(Value::is_string)) + .count(); + if digest_strings == 1 { return Err(protocol_error( AgentRuntimeToolPlanProtocolErrorKind::ArgumentsSchema, - "Agent 原生工具协议错误:agent.delegate 澄清 continuation 字段必须同时提供", + "Agent 原生工具协议错误:agent.delegate 澄清 continuation 指纹必须成对提供,或整体省略交给 Runtime 补齐", + )); + } + if digest_strings == 2 && !continuation_anchor { + return Err(protocol_error( + AgentRuntimeToolPlanProtocolErrorKind::ArgumentsSchema, + "Agent 原生工具协议错误:agent.delegate 澄清 continuation 指纹必须同时提供 continuationOfDelegationId", )); } for field in ["questionsSha256", "answersSha256"] { @@ -2070,19 +2069,43 @@ mod tests { } #[test] - fn native_agent_delegate_rejects_partial_or_invalid_clarification_binding() { - let mut partial = valid_delegate_input(json!("delegation-id"), Value::Null); - partial + fn native_agent_delegate_accepts_clarification_continuation_without_fingerprints() { + // 指纹由 Runtime 从原 delivery 补齐,Supervisor 只需要指出续跑的是哪个委派。 + let mut anchor_only = valid_delegate_input(json!("delegation-id"), Value::Null); + anchor_only .as_object_mut() .expect("delegate input object") .insert( "continuationOfDelegationId".to_string(), json!("delegation-id"), ); - assert!(validate_native_agent_delegate_input(&partial) - .expect_err("partial continuation binding must fail") + + validate_native_agent_delegate_input(&anchor_only) + .expect("clarification continuation without fingerprints"); + } + + #[test] + fn native_agent_delegate_rejects_partial_or_invalid_clarification_binding() { + let mut single_digest = valid_delegate_input(json!("delegation-id"), Value::Null); + let object = single_digest.as_object_mut().expect("delegate input object"); + object.insert( + "continuationOfDelegationId".to_string(), + json!("delegation-id"), + ); + object.insert("questionsSha256".to_string(), json!("a".repeat(64))); + assert!(validate_native_agent_delegate_input(&single_digest) + .expect_err("single continuation fingerprint must fail") .to_string() - .contains("必须同时提供")); + .contains("必须成对提供")); + + let mut orphan_digests = valid_delegate_input(json!("delegation-id"), Value::Null); + let object = orphan_digests.as_object_mut().expect("delegate input object"); + object.insert("questionsSha256".to_string(), json!("a".repeat(64))); + object.insert("answersSha256".to_string(), json!("b".repeat(64))); + assert!(validate_native_agent_delegate_input(&orphan_digests) + .expect_err("continuation fingerprints without anchor must fail") + .to_string() + .contains("必须同时提供 continuationOfDelegationId")); let mut invalid_sha = valid_delegate_input(json!("delegation-id"), Value::Null); let object = invalid_sha.as_object_mut().expect("delegate input object"); diff --git a/apps/ai-game-creator-shell/src-tauri/src/delegation.rs b/apps/ai-game-creator-shell/src-tauri/src/delegation.rs index e15f04363..e1505a4db 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/delegation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/delegation.rs @@ -1553,7 +1553,13 @@ pub(crate) fn validate_static_delegate_clarification_continuation_at( if continuation_of != repair_of_delegation_id { return Err("澄清 continuation 必须绑定原 delegationId".to_string()); } - if input_questions_sha != questions_sha256 || input_answers_sha != answers_sha256 { + // 两个指纹是可选的:原 delivery 已经唯一确定了它们,下面的 continuation identity + // 也只用这份权威值算,输入侧填了只是重复一遍。要求 Supervisor 手抄 128 个十六进制 + // 字符没有任何信息增益,抄错却会一路打到硬失败,所以缺省时由 Runtime 自己补齐。 + // 填了就仍然逐字校验——它能证明本轮续跑对应的确实是这次已回答的请求。 + if (!input_questions_sha.is_empty() && input_questions_sha != questions_sha256) + || (!input_answers_sha.is_empty() && input_answers_sha != answers_sha256) + { return Err("澄清 continuation 的问题或答案指纹与已回答请求不一致".to_string()); } let continuation_identity = format!( diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/static_deliveries.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/static_deliveries.rs index 3f59a1cb8..c1f1a4e61 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/static_deliveries.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/static_deliveries.rs @@ -298,6 +298,18 @@ fn clarification_continuation_delegation_id( ) .expect("validate continuation binding") .expect("derived continuation identity"); + // 省略两个指纹时 Runtime 必须从原 delivery 补齐权威值,并得到与手填时完全相同的身份。 + // 这条断言挂在共享 helper 上,所有澄清 continuation 用例都会顺带覆盖到。 + let identity_without_fingerprints = validate_static_delegate_clarification_continuation_at( + root, + parent_agent_id, + parent_run_id, + &serde_json::json!({ "continuationOfDelegationId": original_delegation_id }), + Some(original_delegation_id), + ) + .expect("validate continuation binding without fingerprints") + .expect("derived continuation identity without fingerprints"); + assert_eq!(identity, identity_without_fingerprints); agent_runtime_delegation_id(parent_agent_id, parent_run_id, target_agent_id, &identity) }