补齐Agent Runtime工具计划强杀恢复门禁
新增真实 Provider 工具计划交接 checkpoint、Runner 强杀恢复与零重放验收。 收紧 Supervisor 首波协作、返工 runId 和持久 delivery 合同。 扩展敏感正文扫描、跨平台安全回归和 E2E 自测。 注册两级 npm 命令并同步 Runtime 验收文档与当前证据。
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
"agent-runtime:supervisor-swarm-autonomous-chat-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-autonomous-chat",
|
||||
"agent-runtime:supervisor-swarm-transient-retry-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-transient-retry",
|
||||
"agent-runtime:supervisor-swarm-final-reply-transient-retry-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-final-reply-transient-retry",
|
||||
"agent-runtime:supervisor-swarm-tool-plan-handoff-runner-kill-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-tool-plan-handoff-runner-kill",
|
||||
"agent-runtime:steer-real-e2e": "node scripts/agent-runtime-steer-real-e2e.mjs",
|
||||
"agent-runtime:steer-runner-kill-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite steer-runner-kill",
|
||||
"typecheck": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json --noEmit && node scripts/check-config.mjs"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -492,7 +492,18 @@ fn validate_native_agent_delegate_input(
|
||||
160,
|
||||
true,
|
||||
)?;
|
||||
validate_native_delegate_string(object.get("runId"), "runId", 160, true)
|
||||
validate_native_delegate_string(object.get("runId"), "runId", 160, true)?;
|
||||
if object
|
||||
.get("repairOfDelegationId")
|
||||
.is_some_and(Value::is_string)
|
||||
&& !object.get("runId").is_some_and(Value::is_null)
|
||||
{
|
||||
return Err(protocol_error(
|
||||
AgentRuntimeToolPlanProtocolErrorKind::ArgumentsSchema,
|
||||
"Agent 原生工具协议错误:agent.delegate 返工委派时 runId 必须为 JSON null",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_native_delegate_string(
|
||||
@@ -685,7 +696,9 @@ fn runtime_tool_description(tool: &str) -> &'static str {
|
||||
"canvas.asset_generate" => "通过已配置平台生成并登记首版美术素材。",
|
||||
"blackboard.write" => "向项目级共享黑板追加稳定结论。",
|
||||
"agent.message" => "向一个目标 Agent 写入定向上下文消息。",
|
||||
"agent.delegate" => "用持久验收合同把边界清晰的后台任务委派给另一个 Agent。",
|
||||
"agent.delegate" => {
|
||||
"用持久验收合同把边界清晰的后台任务委派给另一个 Agent;返工时 repairOfDelegationId 指向原 delivery,且 runId 必须为 null。"
|
||||
}
|
||||
"agent.spawn_isolated" => "创建最多三个写范围互不重叠的隔离子 Agent。",
|
||||
"agent.schedule_ready" => "调度依赖已完成的 ready manifest 任务。",
|
||||
"agent.action_history" => "查询当前 Agent 的持久终态动作历史。",
|
||||
@@ -1035,3 +1048,66 @@ fn project_patchset_input_schema() -> Value {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn valid_delegate_input(repair_of_delegation_id: Value, run_id: Value) -> Value {
|
||||
json!({
|
||||
"agentId": "specialist",
|
||||
"task": "完成委派任务",
|
||||
"acceptanceCriteria": ["定向测试通过"],
|
||||
"expectedArtifacts": [],
|
||||
"repairOfDelegationId": repair_of_delegation_id,
|
||||
"runId": run_id,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_agent_delegate_repair_rejects_string_run_id() {
|
||||
let repair_id = "delegation-value-must-not-leak";
|
||||
let run_id = "run-value-must-not-leak";
|
||||
let error = validate_native_agent_delegate_input(&valid_delegate_input(
|
||||
json!(repair_id),
|
||||
json!(run_id),
|
||||
))
|
||||
.expect_err("repair delegate must not accept a string runId");
|
||||
|
||||
assert_eq!(
|
||||
error.kind(),
|
||||
AgentRuntimeToolPlanProtocolErrorKind::ArgumentsSchema
|
||||
);
|
||||
let detail = error.to_string();
|
||||
assert_eq!(
|
||||
detail,
|
||||
"Agent 原生工具协议错误:agent.delegate 返工委派时 runId 必须为 JSON null"
|
||||
);
|
||||
assert!(!detail.contains(repair_id));
|
||||
assert!(!detail.contains(run_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_agent_delegate_repair_accepts_null_run_id() {
|
||||
let input = valid_delegate_input(json!("delegation-id"), Value::Null);
|
||||
|
||||
validate_native_agent_delegate_input(&input)
|
||||
.expect("repair delegate should accept a null runId");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_agent_delegate_initial_accepts_string_run_id() {
|
||||
let input = valid_delegate_input(Value::Null, json!("initial-run-id"));
|
||||
|
||||
validate_native_agent_delegate_input(&input)
|
||||
.expect("initial delegate should accept a valid string runId");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_agent_delegate_description_explains_repair_run_identity() {
|
||||
let description = runtime_tool_description("agent.delegate");
|
||||
|
||||
assert!(description.contains("repairOfDelegationId 指向原 delivery"));
|
||||
assert!(description.contains("runId 必须为 null"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -778,12 +778,26 @@ pub(crate) fn preflight_supervisor_collaboration_plan(
|
||||
policy: &SupervisorCollaborationPolicy,
|
||||
state: &SupervisorCollaborationState,
|
||||
) -> Result<SupervisorCollaborationPreflight, String> {
|
||||
if agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID || actions.is_empty() {
|
||||
if agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
|
||||
return Ok(SupervisorCollaborationPreflight::default());
|
||||
}
|
||||
let policy = normalize_supervisor_collaboration_policy(policy.clone())?;
|
||||
let summary = summarize_supervisor_collaboration_actions(actions)?;
|
||||
let initial_wave = !state.has_collaboration();
|
||||
|
||||
if actions.is_empty() {
|
||||
if initial_wave && supervisor_collaboration_policy_has_initial_requirements(&policy) {
|
||||
return Ok(SupervisorCollaborationPreflight {
|
||||
violation: Some(SupervisorCollaborationViolation {
|
||||
summary: "Project Supervisor 首批协作不能停留在计划更新".to_string(),
|
||||
detail: "当前父 run 尚无协作事实,且项目 policy 明确要求首批专业协作;首批不能停留在计划更新,必须在同一 Provider 批次完整提交协作。".to_string(),
|
||||
}),
|
||||
..SupervisorCollaborationPreflight::default()
|
||||
});
|
||||
}
|
||||
return Ok(SupervisorCollaborationPreflight::default());
|
||||
}
|
||||
|
||||
let summary = summarize_supervisor_collaboration_actions(actions)?;
|
||||
let has_collaboration_action = summary.has_collaboration_action();
|
||||
|
||||
if !initial_wave
|
||||
@@ -1363,6 +1377,58 @@ mod tests {
|
||||
assert!(error.contains("minIsolatedGroupsBeforeClaim 不能超过 16"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supervisor_collaboration_policy_blocks_empty_required_initial_wave() {
|
||||
let result = preflight_supervisor_collaboration_plan(
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
&[],
|
||||
&mixed_policy(),
|
||||
&SupervisorCollaborationState::default(),
|
||||
)
|
||||
.expect("preflight empty required initial wave");
|
||||
let violation = result
|
||||
.violation
|
||||
.expect("required initial wave must reject empty actions");
|
||||
assert_eq!(
|
||||
violation.summary,
|
||||
"Project Supervisor 首批协作不能停留在计划更新"
|
||||
);
|
||||
assert!(violation.detail.contains("首批不能停留在计划更新"));
|
||||
assert!(violation
|
||||
.detail
|
||||
.contains("必须在同一 Provider 批次完整提交协作"));
|
||||
assert!(result.contract.is_none());
|
||||
assert!(!result.force_durable_batch);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supervisor_collaboration_policy_allows_empty_actions_after_collaboration() {
|
||||
let state = SupervisorCollaborationState {
|
||||
initial_static_agent_ids: vec!["design-director".to_string()],
|
||||
..SupervisorCollaborationState::default()
|
||||
};
|
||||
let result = preflight_supervisor_collaboration_plan(
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
&[],
|
||||
&mixed_policy(),
|
||||
&state,
|
||||
)
|
||||
.expect("preflight empty actions after collaboration");
|
||||
assert_eq!(result, SupervisorCollaborationPreflight::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supervisor_collaboration_policy_allows_empty_actions_without_initial_requirements() {
|
||||
let result = preflight_supervisor_collaboration_plan(
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
&[],
|
||||
&SupervisorCollaborationPolicy::default(),
|
||||
&SupervisorCollaborationState::default(),
|
||||
)
|
||||
.expect("preflight empty actions without initial requirements");
|
||||
assert_eq!(result, SupervisorCollaborationPreflight::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supervisor_collaboration_policy_blocks_partial_mixed_wave() {
|
||||
let result = preflight_supervisor_collaboration_plan(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user