补齐总控静态与隔离混合协作验收

新增同一 Supervisor Session/run 下 static delegate 与 isolated all-join 的真实 Provider 门禁。
补齐首批确认、并行重叠、强杀恢复、持久化身份和零泄漏审计。
增加双向完成屏障回归、命令入口及 Runtime/开发文档记录。
修正原生计划元调用与业务 action 数量的验收边界。
This commit is contained in:
AIGameCreator App
2026-07-17 16:37:26 +08:00
parent 4474fb9eab
commit c607451edd
9 changed files with 2298 additions and 192 deletions
+1
View File
@@ -15,6 +15,7 @@
"agent-run": "node scripts/run-cli-with-config.mjs --agent-run",
"agent-run:smoke": "node scripts/smoke-agent-run-local-provider.mjs",
"agent-runtime:real-e2e": "node scripts/agent-runtime-real-e2e.mjs",
"agent-runtime:mixed-swarm-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-static-isolated-autonomous-chat",
"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:steer-real-e2e": "node scripts/agent-runtime-steer-real-e2e.mjs",
File diff suppressed because it is too large Load Diff
@@ -33704,7 +33704,7 @@ pub(crate) fn game_creator_agent_runtime_tool_plan_system_prompt_for_agent(
return prompt;
}
let prompt = format!(
"{prompt}\n\n你当前是项目唯一面向用户的 Project Supervisor,并拥有最终回复权。每一轮都必须把用户原始目标视为最高层业务目标,专业 Agent 回执只能补充证据,不能把回执内容改写成新目标。总控不能替代已有专业角色完成其领域交付:只要仓库目标同时包含两个以上互不依赖的专业方向,就必须自行查看静态角色目录,选择最匹配的不同专业 Agent,并在同一个 native planning 批次用带 acceptanceCriteria 和 expectedArtifacts 的 agent.delegate 发起委派,让这些方向并行;用户不需要点名 Agent、指定数量或提醒并行。只有没有匹配专业角色、纯协调工作或一两步轻量读取时才由总控直接处理。互不重叠的临时并行检查通过 agent.spawn_isolated 分派;已有委派未收束时不要重复委派。需要等待专业 Agent 时返回空 response,让 Runtime 的 delegate/all-join 完成屏障保持同一父 run;取得 readyDelegateReceipts 或 readyIsolatedJoins 后直接整合结果。readyDelegateReceipts 中 contractStatus=evidence-ready 只说明终态、产物和验证等客观证据齐全,你仍须按 acceptanceCriteria 判断语义是否满足;needs-repair 不得当作成功。客观或语义不满足时可以发起一次新 agent.delegate,并把 repairOfDelegationId 指向已认领原 delivery;不得对返工再返工或为同一原 delivery 创建第二个返工。专业结果冲突且无法依据用户目标裁决时,合并问题后用一次 user.input_request 询问用户。只有实现路径、产品取舍或缺失事实会实质改变结果时才调用 user.input_request;项目内可读取事实、权限确认和工具失败不得伪装成用户问题。只在所有必要回执已认领、所有必要返工也已认领、项目副作用已验证且没有待确认动作或待回答请求时给用户最终回复。不要向用户暴露内部 task/event、工具计划、动态 child ID 或调试状态。"
"{prompt}\n\n你当前是项目唯一面向用户的 Project Supervisor,并拥有最终回复权。每一轮都必须把用户原始目标视为最高层业务目标,专业 Agent 回执只能补充证据,不能把回执内容改写成新目标。总控不能替代已有专业角色完成其领域交付:只要仓库目标同时包含两个以上互不依赖的专业方向,就必须自行查看静态角色目录,选择最匹配的不同专业 Agent,并在同一个 native planning 批次用带 acceptanceCriteria 和 expectedArtifacts 的 agent.delegate 发起委派,让这些方向并行;用户不需要点名 Agent、指定数量或提醒并行。只有没有匹配专业角色、纯协调工作或一两步轻量读取时才由总控直接处理。互不重叠的临时并行检查通过 agent.spawn_isolated 分派;当同一目标同时需要边界清晰的专业委派和互不重叠的临时检查时,必须把两类协作放进同一个 native planning 批次一次性提交,不能拆成先后轮次。提交首个协作批次前,先分别完整枚举当前目标中的长期专业交付和临时隔离检查;两类都非空时,遗漏任一类的批次都不得提交。已有委派未收束时不要重复委派。需要等待专业 Agent 时返回空 response,让 Runtime 的 delegate/all-join 完成屏障保持同一父 run;取得 readyDelegateReceipts 或 readyIsolatedJoins 后直接整合结果。readyDelegateReceipts 中 contractStatus=evidence-ready 只说明终态、产物和验证等客观证据齐全,你仍须按 acceptanceCriteria 判断语义是否满足;needs-repair 不得当作成功。客观或语义不满足时可以发起一次新 agent.delegate,并把 repairOfDelegationId 指向已认领原 delivery;不得对返工再返工或为同一原 delivery 创建第二个返工。专业结果冲突且无法依据用户目标裁决时,合并问题后用一次 user.input_request 询问用户。只有实现路径、产品取舍或缺失事实会实质改变结果时才调用 user.input_request;项目内可读取事实、权限确认和工具失败不得伪装成用户问题。只在所有必要回执已认领、所有必要返工也已认领、项目副作用已验证且没有待确认动作或待回答请求时给用户最终回复。不要向用户暴露内部 task/event、工具计划、动态 child ID 或调试状态。"
);
format!(
"{prompt}\n\n普通 agent.run_status 的 claimedDelegateContracts 只提供已认领合同目录。语义复核或返工前必须用原 delegationId 再调用 agent.run_status,读取 claimedDelegateContract 中未截断的 acceptanceCriteria 和 expectedArtifacts,并在 repair agent.delegate 中逐项原样提交。若返工因合同未完整继承而失败,失败 observation 中的 claimedDelegateContract 是同一 durable delivery 的权威快照,必须逐项据此修正;只有该字段缺失或身份不确定时才按同一 delegationId 重读,不得无目标地重复 run_status 或从 action_history 摘要猜测。"
@@ -14768,6 +14768,197 @@ async fn project_supervisor_mixed_waiting_recovery_does_not_plan_until_all_join_
fs::remove_dir_all(root).ok();
}
#[tokio::test]
async fn project_supervisor_mixed_waiting_recovery_does_not_plan_until_static_delivery_ready() {
use platform_agent::game_creation::{
GameCreationIsolatedAgentArtifact, GameCreationIsolatedAgentChildResult,
GameCreationIsolatedAgentChildSpec, GameCreationIsolatedAgentJoinMode,
GameCreationIsolatedAgentResultStatus, GameCreationIsolatedAgentSpawnRequest,
};
let root = unique_project_path();
init_local_game_project_at(&root, "project-1", "混合委派反向等待恢复测试")
.expect("project init");
let (sender, receiver) = mpsc::channel();
let base_url = spawn_mock_llm_server_responses_with_capture(
vec![final_tool_plan_response(
"不应在 static delivery ready 前请求 Provider",
)],
Some(sender),
);
let _config_guard = write_test_local_config(format!(
r#"{{
"agentLlm": {{
"project-supervisor": {{
"apiKey": "mixed-reverse-waiting-key",
"baseUrl": {base_url:?},
"model": "mixed-reverse-waiting-model",
"apiKind": "openai_responses"
}}
}}
}}"#
));
let task = "等待已认领的动态隔离 all-join 与尚未完成的静态专业回执";
let parent_run_id = "project-supervisor-mixed-reverse-waiting-run";
let mut parent_state = start_game_creator_agent_runtime_task_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
task,
parent_run_id,
"agent-chat",
"等待反向混合委派",
vec!["取得两类交付后统一收束".to_string()],
)
.expect("start reverse mixed waiting parent");
let static_delivery = new_static_delegate_delivery(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&parent_state.session_id,
parent_run_id,
"project-supervisor-mixed-reverse-static-action",
"project-supervisor-mixed-reverse-static-delivery",
"design-director",
"project-supervisor-mixed-reverse-static-session",
"project-supervisor-mixed-reverse-static-run",
);
create_or_read_static_delegate_delivery_at(&root, &static_delivery)
.expect("create waiting static delivery");
fs::create_dir_all(root.join("game/mixed-reverse")).expect("create mixed evidence directory");
fs::write(
root.join("game/mixed-reverse/evidence.txt"),
"mixed reverse evidence\n",
)
.expect("write mixed evidence");
let isolated_request = GameCreationIsolatedAgentSpawnRequest {
children: vec![GameCreationIsolatedAgentChildSpec {
template_agent_id: "code-prototype".to_string(),
task: "完成独立 mixed 只读检查".to_string(),
acceptance_criteria: vec!["mixed 只读检查已完成".to_string()],
expected_artifacts: vec!["game/mixed-reverse/evidence.txt".to_string()],
write_scopes: vec!["game/mixed-reverse/**".to_string()],
}],
join_mode: GameCreationIsolatedAgentJoinMode::All,
};
let group = create_or_read_isolated_group_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
parent_run_id,
&parent_state.session_id,
"project-supervisor-mixed-reverse-isolated-action",
&isolated_request,
)
.expect("create reverse mixed isolated group");
let instance = resolve_isolated_agent_instance_at(&root, &group.instance_ids[0])
.expect("resolve reverse mixed isolated instance");
record_isolated_child_result_at(
&root,
&GameCreationIsolatedAgentChildResult {
delegation_id: instance.delegation_id.clone(),
instance_id: instance.instance_id.clone(),
template_agent_id: instance.template_agent_id.clone(),
run_id: instance.run_id.clone(),
status: GameCreationIsolatedAgentResultStatus::Completed,
summary: "mixed 只读检查已完成".to_string(),
artifacts: vec![GameCreationIsolatedAgentArtifact {
path: "game/mixed-reverse/evidence.txt".to_string(),
sha256: "c".repeat(64),
}],
evidence: Vec::new(),
verified_revision: None,
error: None,
},
)
.expect("record reverse mixed isolated result")
.expect("reverse mixed isolated join ready");
let claim_action_id = "project-supervisor-mixed-reverse-run-status-action";
let claimed_observation = observe_agent_runtime_run_status(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
parent_run_id,
Some(claim_action_id),
&serde_json::json!({ "scope": "self" }),
);
assert_eq!(claimed_observation.status, "ok");
assert!(claimed_observation
.detail
.as_deref()
.is_some_and(|detail| detail.contains("readyIsolatedJoins")));
assert!(isolated_join_completion_barrier_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
parent_run_id,
)
.expect("read claimed isolated barrier")
.is_none());
let waiting_static = static_delegate_completion_barrier_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
parent_run_id,
)
.expect("read waiting static barrier");
assert_eq!(waiting_static.waiting_count, 1);
parent_state.status = "running".to_string();
parent_state.phase = "waiting-for-isolated-join".to_string();
parent_state.current_action = "等待动态隔离 Agent 的 all-join".to_string();
parent_state.waiting_on = "静态与隔离 Agent 完成".to_string();
parent_state.next_step = "保持等待,不请求 Provider".to_string();
parent_state.loop_iteration = 5;
parent_state.max_loop_iterations = 18;
append_game_creator_agent_runtime_task(&root, &parent_state)
.expect("append reverse mixed waiting parent task");
write_game_creator_agent_runtime_state(&root, &parent_state)
.expect("persist reverse mixed waiting parent state");
let observations = vec![claimed_observation];
let context = build_game_creator_agent_runtime_context_bundle(
&root,
&parent_state,
task,
&AgentRuntimeToolPlan::default(),
&observations,
5,
&AgentRuntimeContextWindowTracker::default(),
)
.expect("build reverse mixed waiting context");
write_game_creator_agent_runtime_context_bundle(&root, &context)
.expect("persist reverse mixed waiting context");
resume_game_creator_agent_background_tasks_at(&root)
.expect("resume reverse mixed waiting parent");
let deadline = std::time::Instant::now() + Duration::from_secs(2);
loop {
let runtime =
read_game_creator_agent_runtime_at(&root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID)
.expect("read reverse mixed waiting parent")
.state;
if runtime.phase == "waiting-for-delegate-receipts" {
assert_eq!(runtime.run_id, parent_run_id);
assert_eq!(runtime.loop_iteration, 5);
assert_eq!(runtime.max_loop_iterations, 18);
break;
}
assert!(
std::time::Instant::now() < deadline,
"mixed parent did not switch to static waiting"
);
tokio::time::sleep(Duration::from_millis(10)).await;
}
assert!(receiver.recv_timeout(Duration::from_millis(200)).is_err());
resume_game_creator_agent_background_tasks_at(&root)
.expect("repeat reverse mixed waiting parent resume");
assert!(receiver.recv_timeout(Duration::from_millis(200)).is_err());
let stable =
read_game_creator_agent_runtime_at(&root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID)
.expect("read stable reverse mixed waiting parent")
.state;
assert_eq!(stable.run_id, parent_run_id);
assert_eq!(stable.phase, "waiting-for-delegate-receipts");
assert_eq!(stable.loop_iteration, 5);
fs::remove_dir_all(root).ok();
}
#[test]
fn repository_context_v1_pending_fingerprint_blocks_project_mutation() {
let root = unique_project_path();
@@ -45182,6 +45373,8 @@ async fn project_supervisor_prompts_are_total_control_and_reject_isolated_templa
"专业 Agent 回执只能补充证据",
"总控不能替代已有专业角色",
"同一个 native planning 批次",
"必须把两类协作放进同一个 native planning 批次一次性提交",
"两类都非空时,遗漏任一类的批次都不得提交",
"用户不需要点名 Agent",
"agent.delegate",
"readyDelegateReceipts",