澄清续跑的两个指纹改由 Runtime 补齐,不再让 Supervisor 手抄

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 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 16:44:48 +00:00
parent c489c92084
commit 535eef8b58
4 changed files with 69 additions and 28 deletions
@@ -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 指向该原 delegationquestionsSha256/answersSha256 由 Runtime 从原 delivery 补齐,你不要自己填写",
delivery.delegation_id
);
// Re-entry after a wake or restart may only reuse the exact request that
@@ -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");
@@ -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!(
@@ -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)
}