修复 Native shell 委派协议回归
兼容未携带澄清字段的既有委派工具调用 同步原生委派 schema 契约测试 调整试玩失败门禁优先级并补充反向回归 记录委派协议演进与门禁优先级约束
This commit is contained in:
+61
-6
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user