澄清续跑的两个指纹改由 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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user