修复总控试玩失败后的专业返工收口

总控进入协作后将试玩失败收窄为 code-prototype 后续委派,避免越权直接修改项目
保留无协作兼容路径和新 revision 先复验再试玩的完成门禁
补齐协作分支回归测试并修正自主完成合同测试夹具
同步 Runtime 技术方案、踩坑记录和 Rust 格式
This commit is contained in:
AIGameCreator App
2026-07-21 17:26:16 +08:00
parent 81d9252b6b
commit 1fce5460f9
5 changed files with 373 additions and 79 deletions
+118 -35
View File
@@ -10421,6 +10421,8 @@ const AGENT_RUNTIME_AUTONOMOUS_VERIFIED_DELIVERY_LIVENESS_ERROR_PREFIX: &str =
"自主构建专业 Agent 已取得当前 revision 的通过凭证";
const AGENT_RUNTIME_AUTONOMOUS_FAILED_PLAYTEST_LIVENESS_ERROR_PREFIX: &str =
"自主构建 Project Supervisor 的最近一次交互试玩仍未通过";
const AGENT_RUNTIME_AUTONOMOUS_DELEGATED_PLAYTEST_REPAIR_LIVENESS_ERROR_PREFIX: &str =
"自主构建 Project Supervisor 的最近一次交互试玩需要专业 Agent 修复";
const AGENT_RUNTIME_AUTONOMOUS_RESPONSE_PLAN_LIVENESS_ERROR_PREFIX: &str =
"自主构建专业 Agent 已给出结论但结构化计划仍未完成";
const AGENT_RUNTIME_AUTONOMOUS_TRUNCATED_SCAFFOLD_ERROR_PREFIX: &str =
@@ -17196,7 +17198,10 @@ mod autonomous_completion_contract_tests {
"Next Level",
"Restart",
] {
assert!(prompt.contains(label), "missing lane-defense label: {label}");
assert!(
prompt.contains(label),
"missing lane-defense label: {label}"
);
}
for selector in [
"start",
@@ -17206,7 +17211,10 @@ mod autonomous_completion_contract_tests {
"next-level",
"restart",
] {
assert!(prompt.contains(selector), "missing playtest selector: {selector}");
assert!(
prompt.contains(selector),
"missing playtest selector: {selector}"
);
}
}
@@ -17243,10 +17251,11 @@ mod autonomous_completion_contract_tests {
#[test]
fn autonomous_completion_requires_changed_index_static_smoke_and_bound_playtest() {
let (_temporary, root, state, contract) = autonomous_fixture(
let (_temporary, root, mut state, contract) = autonomous_fixture(
"做一个塔防游戏,选择植物阻挡敌人并正常闯关",
"autonomous-completion-evidence-run",
);
complete_agent_runtime_remaining_plan_steps(&mut state, "测试已完成实现与静态验证");
let blocker = autonomous_game_build_completion_blocker_at_locked(&root, &state)
.expect("baseline project must remain blocked");
assert!(blocker.summary.contains("revision"));
@@ -17265,6 +17274,14 @@ mod autonomous_completion_contract_tests {
let blocker = autonomous_game_build_completion_blocker_at_locked(&root, &state)
.expect("missing playtest receipt must block completion");
assert!(blocker.summary.contains("交互试玩回执"));
bind_supervisor_collaboration_policy_snapshot_at(
&root,
&state.agent_id,
&state.run_id,
&SupervisorCollaborationPolicy::default(),
"legacy-current-project-policy",
)
.expect("isolate autonomous completion gate from collaboration policy");
let outcome = finish_game_creator_agent_background_runtime_turn_at(
&root,
state.clone(),
@@ -17273,11 +17290,14 @@ mod autonomous_completion_contract_tests {
&[],
)
.expect("finalization should return a stale blocker");
assert!(matches!(
outcome,
AgentBackgroundFinalizationOutcome::Stale(ref blocker)
if blocker.tool == "runtime.autonomous_completion"
));
assert!(
matches!(
&outcome,
AgentBackgroundFinalizationOutcome::Stale(ref blocker)
if blocker.tool == "runtime.autonomous_completion"
),
"unexpected finalization outcome: {outcome:?}"
);
assert!(!game_creator_agent_runtime_finalization_path(
&root,
&state.agent_id,
@@ -17492,8 +17512,13 @@ fn validate_agent_runtime_verification_gate(
{
return Err("Agent Runtime verification gate 的 verifiedRevision 早于修改".to_string());
}
if gate.failed_playtest_revision.is_some_and(|revision| revision == 0) {
return Err("Agent Runtime verification gate 的 failedPlaytestRevision 必须大于 0".to_string());
if gate
.failed_playtest_revision
.is_some_and(|revision| revision == 0)
{
return Err(
"Agent Runtime verification gate 的 failedPlaytestRevision 必须大于 0".to_string(),
);
}
if gate
.failed_playtest_revision
@@ -24861,6 +24886,19 @@ async fn request_game_creator_agent_background_tool_plan_at(
read_game_creator_agent_runtime_verification_gate(root, agent_id, run_id)?;
let project_revision =
read_game_creator_agent_runtime_project_revision(root)?.revision;
let supervisor_requires_delegated_repair = if agent_id
== GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
{
let collaboration_policy =
resolve_supervisor_collaboration_policy_for_run_at(root, agent_id, run_id)?
.policy;
let collaboration_state =
read_supervisor_collaboration_state_at(root, agent_id, run_id)?;
collaboration_policy.orchestrator_only_after_delegation
&& collaboration_state.has_collaboration()
} else {
false
};
match validate_agent_runtime_autonomous_plan_liveness(
agent_id,
loop_index,
@@ -24868,6 +24906,7 @@ async fn request_game_creator_agent_background_tool_plan_at(
&verification_gate,
observations,
&parsed.plan,
supervisor_requires_delegated_repair,
) {
Ok(()) => Ok((parsed, source_payload)),
Err(error) => Err(AgentRuntimeToolPlanProtocolError::new(
@@ -25099,12 +25138,17 @@ async fn request_game_creator_agent_background_tool_plan_at(
AGENT_RUNTIME_AUTONOMOUS_FAILED_PLAYTEST_LIVENESS_ERROR_PREFIX,
)
&& !request.function_tools.is_empty();
let force_autonomous_response_plan_completion = run_profile
let force_autonomous_delegated_playtest_repair = run_profile
== AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
&& protocol_error.starts_with(
AGENT_RUNTIME_AUTONOMOUS_RESPONSE_PLAN_LIVENESS_ERROR_PREFIX,
AGENT_RUNTIME_AUTONOMOUS_DELEGATED_PLAYTEST_REPAIR_LIVENESS_ERROR_PREFIX,
)
&& !request.function_tools.is_empty();
let force_autonomous_response_plan_completion = run_profile
== AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
&& protocol_error
.starts_with(AGENT_RUNTIME_AUTONOMOUS_RESPONSE_PLAN_LIVENESS_ERROR_PREFIX)
&& !request.function_tools.is_empty();
let force_autonomous_truncated_scaffold = run_profile
== AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
&& protocol_error
@@ -25116,6 +25160,7 @@ async fn request_game_creator_agent_background_tool_plan_at(
) && !request.function_tools.is_empty();
if force_supervisor_initial_collaboration
|| force_autonomous_response_plan_completion
|| force_autonomous_delegated_playtest_repair
|| force_autonomous_failed_playtest
|| force_autonomous_pending_verification
|| force_autonomous_reverify_after_mutation
@@ -25136,6 +25181,13 @@ async fn request_game_creator_agent_background_tool_plan_at(
request.messages.push(LlmMessage::user(format!(
"上一条输出不符合工具计划协议:{protocol_error}\n本次修复的原生工具目录只保留 update_agent_plan 与 respond_to_user。必须在同一响应先调用 update_agent_plan,保留原步骤标题并把已经真实完成的全部步骤标为 completed,再调用 respond_to_user 交付刚才已经形成的结论。不得只调用其中一个函数,不得新增步骤、继续读取、修改项目或解释。"
)));
} else if force_autonomous_delegated_playtest_repair {
restrict_agent_runtime_autonomous_delegated_playtest_repair_tools(
&mut request,
)?;
request.messages.push(LlmMessage::user(format!(
"上一条输出不符合工具计划协议:{protocol_error}\n当前父 run 已进入只编排模式,本次修复的原生工具目录只保留 agent.delegate。必须立即向 code-prototype 创建一个新的后续修复委派,把最近一次 preview.validate 的全部失败诊断写入 task 和 acceptanceCriteria,expectedArtifacts 必须包含 game/index.html;repairOfDelegationId 与 runId 都设为 null,由专业 Agent 产生新的 revision。该任务是对新发现试玩缺口的后续修复,不得对已返工 delivery 再返工。不得直接修改项目、更新计划、读取、搜索、重复验证、查询状态或 respond_to_user。不要解释,不要 markdown,不要代码围栏。"
)));
} else if force_autonomous_failed_playtest {
restrict_agent_runtime_autonomous_failed_playtest_repair_tools(&mut request)?;
request.messages.push(LlmMessage::user(format!(
@@ -28313,12 +28365,8 @@ fn validate_agent_runtime_autonomous_response_plan_completion(
if allow_runtime_plan_completion {
return Ok(());
}
let mut runtime = read_game_creator_agent_runtime_for_session_at(
root,
agent_id,
Some(session_id),
)?
.state;
let mut runtime =
read_game_creator_agent_runtime_for_session_at(root, agent_id, Some(session_id))?.state;
if let Some(update) = plan.plan_update.as_ref() {
apply_agent_runtime_plan_update(&mut runtime, update).map_err(|error| {
format!(
@@ -28347,6 +28395,7 @@ fn validate_agent_runtime_autonomous_plan_liveness(
verification_gate: &AgentRuntimeVerificationGate,
observations: &[AgentRuntimeToolObservation],
plan: &AgentRuntimeToolPlan,
supervisor_requires_delegated_repair: bool,
) -> Result<(), String> {
let has_mutation = plan
.actions
@@ -28358,19 +28407,31 @@ fn validate_agent_runtime_autonomous_plan_liveness(
"project.verify" | "command.run_limited" | "command.exec"
)
});
let has_specialist_delegation = plan
.actions
.iter()
.any(|action| action.tool.trim() == "agent.delegate");
if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
if let Some(failed_playtest_revision) = verification_gate.failed_playtest_revision {
if project_revision < failed_playtest_revision {
return Err("自主构建持久试玩失败 revision 晚于当前项目 revision".to_string());
}
if project_revision > failed_playtest_revision {
if has_mutation || has_verification {
if has_verification || (!supervisor_requires_delegated_repair && has_mutation) {
return Ok(());
}
return Err(format!(
"{AGENT_RUNTIME_AUTONOMOUS_REVERIFY_AFTER_MUTATION_LIVENESS_ERROR_PREFIX};试玩失败后项目已从 revision {failed_playtest_revision} 推进到 {project_revision},必须先验证当前 revision,才能重新执行 preview.validate;不得继续只更新计划、读取、搜索、查询状态、委派或返回最终回复"
));
}
if supervisor_requires_delegated_repair {
if has_specialist_delegation {
return Ok(());
}
return Err(format!(
"{AGENT_RUNTIME_AUTONOMOUS_DELEGATED_PLAYTEST_REPAIR_LIVENESS_ERROR_PREFIX};持久验证门仍标记 revision {failed_playtest_revision} 的试玩失败。当前父 run 已进入只编排模式,必须立即把最近一次 preview.validate 诊断委派给 code-prototype 创建新的后续修复 revision;不得直接修改项目、只更新计划、读取、搜索、重复验证、查询状态或返回最终回复"
));
}
if !has_mutation {
return Err(format!(
"{AGENT_RUNTIME_AUTONOMOUS_FAILED_PLAYTEST_LIVENESS_ERROR_PREFIX};持久验证门仍标记 revision {failed_playtest_revision} 的试玩失败,即使上下文压缩后也必须根据最近一次试玩诊断直接修改 game/index.html;不得继续只更新计划、读取、搜索、重复验证、查询状态、委派或返回最终回复"
@@ -28389,11 +28450,18 @@ fn validate_agent_runtime_autonomous_plan_liveness(
.iter()
.any(is_agent_runtime_project_mutation_observation)
|| has_mutation
|| (supervisor_requires_delegated_repair && has_specialist_delegation)
|| observations_after_failure.len()
< AGENT_RUNTIME_AUTONOMOUS_LIVENESS_OBSERVATION_LIMIT
{
return Ok(());
}
if supervisor_requires_delegated_repair {
return Err(format!(
"{AGENT_RUNTIME_AUTONOMOUS_DELEGATED_PLAYTEST_REPAIR_LIVENESS_ERROR_PREFIX};失败后已有 {} 条非推进观察,本响应仍未委派专业修复。当前父 run 已进入只编排模式,必须立即把最近一次 preview.validate 诊断委派给 code-prototype 创建新的后续修复 revision;不得直接修改项目、只更新计划、读取、搜索、重复验证、查询状态或返回最终回复",
observations_after_failure.len()
));
}
return Err(format!(
"{AGENT_RUNTIME_AUTONOMOUS_FAILED_PLAYTEST_LIVENESS_ERROR_PREFIX};失败后已有 {} 条非修改观察,本响应仍未提交项目修改。必须根据 preview.validate 的持久诊断直接修复 game/index.html;不得继续只更新计划、读取、搜索、重复验证、查询状态、委派或返回最终回复",
observations_after_failure.len()
@@ -28426,7 +28494,8 @@ fn validate_agent_runtime_autonomous_plan_liveness(
return Ok(());
}
let latest_progress_is_mutation = latest_mutation_index.is_some_and(|mutation_index| {
latest_verification_index.is_none_or(|verification_index| mutation_index > verification_index)
latest_verification_index
.is_none_or(|verification_index| mutation_index > verification_index)
});
let error_prefix = if latest_progress_is_mutation {
AGENT_RUNTIME_AUTONOMOUS_REVERIFY_AFTER_MUTATION_LIVENESS_ERROR_PREFIX
@@ -28581,10 +28650,12 @@ fn restrict_agent_runtime_autonomous_response_plan_repair_tools(
AGENT_RUNTIME_UPDATE_PLAN_FUNCTION_NAME,
AGENT_RUNTIME_RESPOND_FUNCTION_NAME,
] {
if !request.function_tools.iter().any(|tool| tool.name == required) {
return Err(format!(
"autonomous 计划收束修复工具目录缺少 {required}"
));
if !request
.function_tools
.iter()
.any(|tool| tool.name == required)
{
return Err(format!("autonomous 计划收束修复工具目录缺少 {required}"));
}
}
request.max_output_tokens = Some(
@@ -28727,6 +28798,26 @@ fn restrict_agent_runtime_autonomous_failed_playtest_repair_tools(
Ok(())
}
fn restrict_agent_runtime_autonomous_delegated_playtest_repair_tools(
request: &mut LlmRunRequest,
) -> Result<(), String> {
let delegate_function = native_runtime_function_name("agent.delegate")
.ok_or_else(|| "无法生成自主试玩专业修复委派工具名".to_string())?;
request
.function_tools
.retain(|tool| tool.name == delegate_function);
if request.function_tools.len() != 1 {
return Err("自主试玩专业修复工具目录缺少 agent.delegate".to_string());
}
request.max_output_tokens = Some(
request
.max_output_tokens
.unwrap_or(AGENT_RUNTIME_AUTONOMOUS_FORCED_ACTION_MAX_OUTPUT_TOKENS)
.min(AGENT_RUNTIME_AUTONOMOUS_FORCED_ACTION_MAX_OUTPUT_TOKENS),
);
Ok(())
}
fn restrict_agent_runtime_autonomous_reverification_repair_tools(
request: &mut LlmRunRequest,
) -> Result<(), String> {
@@ -28808,10 +28899,7 @@ fn apply_agent_runtime_autonomous_source_schema_limits_with_max(
max_chars: usize,
) -> Result<(), String> {
let field_pointers = [
(
"file.write",
vec!["/properties/input/properties/content"],
),
("file.write", vec!["/properties/input/properties/content"]),
(
"file.patch",
vec![
@@ -28848,10 +28936,7 @@ fn apply_agent_runtime_autonomous_source_schema_limits_with_max(
"autonomous 源码 schema 字段不是 object:{runtime_tool}{pointer}"
));
};
schema.insert(
"maxLength".to_string(),
serde_json::json!(max_chars),
);
schema.insert("maxLength".to_string(), serde_json::json!(max_chars));
}
}
Ok(())
@@ -28876,9 +28961,7 @@ fn restrict_agent_runtime_supervisor_collaboration_repair_tools(
.iter()
.any(|tool| tool.name == required)
{
return Err(format!(
"Supervisor 首批协作修复工具目录缺少 {required}"
));
return Err(format!("Supervisor 首批协作修复工具目录缺少 {required}"));
}
}
Ok(())
@@ -1834,10 +1834,8 @@ pub(crate) fn append_agent_db_tool_plan_audit_idempotent(
return Err("Agent DB tool-plan 自主源码总长度无效".to_string());
};
if mutation_count > 1
|| max_field_chars
> AGENT_RUNTIME_AUTONOMOUS_SOURCE_FIELD_MAX_CHARS as u64
|| total_chars
> AGENT_RUNTIME_AUTONOMOUS_SOURCE_TOTAL_MAX_CHARS as u64
|| max_field_chars > AGENT_RUNTIME_AUTONOMOUS_SOURCE_FIELD_MAX_CHARS as u64
|| total_chars > AGENT_RUNTIME_AUTONOMOUS_SOURCE_TOTAL_MAX_CHARS as u64
|| total_chars < max_field_chars
{
return Err("Agent DB tool-plan 自主源码载荷审计越界".to_string());
+243 -40
View File
@@ -6787,8 +6787,7 @@ async fn autonomous_game_build_repairs_truncated_scaffold_into_bounded_patch() {
let repairs = records
.iter()
.filter(|record| {
record["recordType"] == "agent.runtime.tool_plan.repair"
&& record["runId"] == run_id
record["recordType"] == "agent.runtime.tool_plan.repair" && record["runId"] == run_id
})
.collect::<Vec<_>>();
assert_eq!(repairs.len(), 1);
@@ -7052,8 +7051,7 @@ async fn autonomous_game_build_repairs_final_response_with_incomplete_plan() {
Some(&child_link),
)
.expect("bind autonomous child profile");
let read_arguments =
serde_json::json!({"reason": "继续重复读取项目", "input": {}}).to_string();
let read_arguments = serde_json::json!({"reason": "继续重复读取项目", "input": {}}).to_string();
let incomplete_response = serde_json::json!({
"response": "只读审查已经完成,当前入口缺少可玩状态合同。"
})
@@ -7154,7 +7152,10 @@ async fn autonomous_game_build_repairs_final_response_with_incomplete_plan() {
.await
.expect("repair autonomous response plan")
.expect("repaired response plan");
assert_eq!(plan.response, "只读审查已经完成,当前入口缺少可玩状态合同。");
assert_eq!(
plan.response,
"只读审查已经完成,当前入口缺少可玩状态合同。"
);
assert!(plan.actions.is_empty());
assert!(plan.plan_update.as_ref().is_some_and(|update| update
.steps
@@ -7251,8 +7252,7 @@ async fn autonomous_game_build_repairs_explicit_read_only_loop_into_completed_de
Some(&child_link),
)
.expect("bind autonomous child profile");
let read_arguments =
serde_json::json!({"reason": "核对当前入口", "input": {}}).to_string();
let read_arguments = serde_json::json!({"reason": "核对当前入口", "input": {}}).to_string();
let response = serde_json::json!({
"response": "当前入口缺少可玩状态合同,需要程序 Agent 完成实现。"
})
@@ -7313,8 +7313,7 @@ async fn autonomous_game_build_repairs_explicit_read_only_loop_into_completed_de
},
)
.expect("persist read-only plan");
write_game_creator_agent_runtime_state(&root, &runtime)
.expect("write read-only runtime");
write_game_creator_agent_runtime_state(&root, &runtime).expect("write read-only runtime");
let plan = request_game_creator_agent_background_tool_plan_for_test(
&root,
@@ -7604,8 +7603,7 @@ async fn autonomous_game_build_repairs_post_mutation_read_loop_into_verification
.expect("bind autonomous child profile");
let (sender, receiver) = mpsc::channel();
let read_arguments = serde_json::json!({"reason": "继续重复读取项目", "input": {}})
.to_string();
let read_arguments = serde_json::json!({"reason": "继续重复读取项目", "input": {}}).to_string();
let verify_arguments = serde_json::json!({
"reason": "停止空转并立即重跑正式验证",
"input": {
@@ -7703,14 +7701,16 @@ async fn autonomous_game_build_repairs_post_mutation_read_loop_into_verification
detail: None,
},
];
observations.extend((0..AGENT_RUNTIME_AUTONOMOUS_LIVENESS_OBSERVATION_LIMIT).map(|_| {
AgentRuntimeToolObservation {
tool: "runtime.plan_update".to_string(),
status: "blocked".to_string(),
summary: "结构化计划尚未完成".to_string(),
detail: None,
}
}));
observations.extend(
(0..AGENT_RUNTIME_AUTONOMOUS_LIVENESS_OBSERVATION_LIMIT).map(|_| {
AgentRuntimeToolObservation {
tool: "runtime.plan_update".to_string(),
status: "blocked".to_string(),
summary: "结构化计划尚未完成".to_string(),
detail: None,
}
}),
);
let plan = request_game_creator_agent_background_tool_plan_for_test(
&root,
@@ -7851,14 +7851,16 @@ async fn autonomous_game_build_repairs_supervisor_failed_playtest_stall_into_mut
summary: "浏览器验证未通过,请根据诊断修复后重试".to_string(),
detail: Some(r#"{"passed":false,"diagnostics":["缺少可玩状态"]}"#.to_string()),
}];
observations.extend((0..AGENT_RUNTIME_AUTONOMOUS_LIVENESS_OBSERVATION_LIMIT).map(|_| {
AgentRuntimeToolObservation {
tool: "runtime.plan_update".to_string(),
status: "blocked".to_string(),
summary: "结构化计划尚未完成".to_string(),
detail: None,
}
}));
observations.extend(
(0..AGENT_RUNTIME_AUTONOMOUS_LIVENESS_OBSERVATION_LIMIT).map(|_| {
AgentRuntimeToolObservation {
tool: "runtime.plan_update".to_string(),
status: "blocked".to_string(),
summary: "结构化计划尚未完成".to_string(),
detail: None,
}
}),
);
let plan = request_game_creator_agent_background_tool_plan_for_test(
&root,
@@ -8115,6 +8117,192 @@ async fn autonomous_game_build_repairs_persisted_failed_playtest_after_context_c
fs::remove_dir_all(root).ok();
}
#[tokio::test]
async fn autonomous_supervisor_delegates_failed_playtest_repair_after_collaboration() {
let root = unique_project_path();
init_local_game_project_at(
&root,
"project-autonomous-supervisor-delegated-playtest-repair",
"自主构建总控委派试玩修复测试",
)
.expect("project init");
let run_id = "autonomous-supervisor-delegated-playtest-repair-run";
bind_game_creator_agent_runtime_run_profile_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
run_id,
AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE,
Some(AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD),
None,
)
.expect("bind autonomous supervisor profile");
let runtime = start_game_creator_agent_runtime_task_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
"委派专业 Agent 修复试玩失败",
run_id,
AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE,
"根据试玩诊断继续专业协作",
vec!["委派试玩修复".to_string(), "重新验证并交付".to_string()],
)
.expect("start autonomous supervisor runtime");
bind_supervisor_collaboration_policy_snapshot_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
run_id,
&SupervisorCollaborationPolicy::default(),
"legacy-current-project-policy",
)
.expect("bind collaboration policy snapshot");
let initial_delivery = new_static_delegate_delivery(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&runtime.session_id,
run_id,
"autonomous-supervisor-initial-delegate-action",
"autonomous-supervisor-initial-delivery",
"quality-review",
"autonomous-supervisor-initial-child-session",
"autonomous-supervisor-initial-child-run",
);
create_or_read_static_delegate_delivery_at(&root, &initial_delivery)
.expect("create existing collaboration fact");
let failed_revision = prepare_agent_runtime_project_mutation_locked(
&root,
"code-prototype",
"autonomous-supervisor-specialist-mutation-run",
"file.patch",
)
.expect("record specialist mutation");
let (expected_revision, gate) = begin_agent_runtime_project_verification_locked(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
run_id,
"game.static_smoke",
)
.expect("begin supervisor static smoke");
finish_agent_runtime_project_verification_locked(&root, &expected_revision, gate, true)
.expect("finish supervisor static smoke");
invalidate_agent_runtime_project_verification_after_preview_failure_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
run_id,
failed_revision,
)
.expect("persist supervisor failed playtest");
let patch_arguments = serde_json::json!({
"reason": "总控误尝试直接修复试玩失败",
"input": {
"path": "game/index.html",
"oldText": "canvas{display:none}",
"newText": "canvas{display:block}"
}
})
.to_string();
let delegate_arguments = serde_json::json!({
"reason": "把最新试玩诊断交给程序专业 Agent",
"input": {
"agentId": "code-prototype",
"task": "修复最近一次 preview.validate 报告的桌面和移动端 canvas 不可见问题,并保持 lane-defense-v1 全部交互断言通过。",
"acceptanceCriteria": [
"桌面和移动端均存在可见且非空的 canvas",
"lane-defense-v1 全部固定试玩断言继续通过",
"修复后通过 game.static_smoke"
],
"expectedArtifacts": ["game/index.html"],
"repairOfDelegationId": null,
"runId": null
}
})
.to_string();
let (sender, receiver) = mpsc::channel();
let base_url = spawn_mock_llm_raw_responses_with_capture(
vec![
native_agent_tool_plan_chat_response(
"call-autonomous-supervisor-blocked-direct-playtest-patch",
&native_runtime_function_name("file.patch").expect("patch function"),
patch_arguments,
),
native_agent_tool_plan_chat_response(
"call-autonomous-supervisor-delegated-playtest-repair",
&native_runtime_function_name("agent.delegate").expect("delegate function"),
delegate_arguments,
),
],
Some(sender),
);
let _config_guard = write_test_local_config(format!(
r#"{{
"agentLlm": {{
"project-supervisor": {{
"apiKey": "autonomous-supervisor-delegated-playtest-repair-key",
"baseUrl": {base_url:?},
"model": "autonomous-supervisor-delegated-playtest-repair-model",
"apiKind": "openai_chat",
"maxRetries": 0
}}
}}
}}"#
));
let plan = request_game_creator_agent_background_tool_plan_for_test(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
&runtime.session_id,
run_id,
&runtime.current_task,
&[],
30,
0,
)
.await
.expect("repair collaborated supervisor playtest stall")
.expect("delegated supervisor playtest repair plan");
assert_eq!(plan.actions.len(), 1);
assert_eq!(plan.actions[0].tool, "agent.delegate");
assert_eq!(
plan.actions[0]
.input
.get("agentId")
.and_then(serde_json::Value::as_str),
Some("code-prototype")
);
receiver
.recv_timeout(Duration::from_secs(2))
.expect("initial direct mutation request");
let repair_request = receiver
.recv_timeout(Duration::from_secs(2))
.expect("delegated playtest repair request");
assert!(repair_request.contains("最近一次交互试玩需要专业 Agent 修复"));
assert!(repair_request.contains("只编排模式"));
let repair_request_json = mock_http_request_json(&repair_request);
let repair_function_names = repair_request_json["tools"]
.as_array()
.expect("restricted delegated playtest repair tools")
.iter()
.filter_map(|tool| {
tool.get("name")
.and_then(serde_json::Value::as_str)
.or_else(|| {
tool.get("function")
.and_then(|function| function.get("name"))
.and_then(serde_json::Value::as_str)
})
})
.collect::<BTreeSet<_>>();
assert_eq!(
repair_function_names,
BTreeSet::from([native_runtime_function_name("agent.delegate")
.expect("delegate function")
.as_str(),])
);
assert!(receiver.recv_timeout(Duration::from_millis(200)).is_err());
fs::remove_dir_all(root).ok();
}
#[tokio::test]
async fn autonomous_game_build_repairs_new_revision_after_failed_playtest_with_verification() {
let root = unique_project_path();
@@ -8147,9 +8335,13 @@ async fn autonomous_game_build_repairs_new_revision_after_failed_playtest_with_v
)
.expect("start autonomous supervisor runtime");
let failed_revision =
prepare_agent_runtime_project_mutation_locked(&root, "code-prototype", "child-run-1", "file.write")
.expect("record specialist mutation");
let failed_revision = prepare_agent_runtime_project_mutation_locked(
&root,
"code-prototype",
"child-run-1",
"file.write",
)
.expect("record specialist mutation");
let (expected_revision, gate) = begin_agent_runtime_project_verification_locked(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
@@ -8166,13 +8358,16 @@ async fn autonomous_game_build_repairs_new_revision_after_failed_playtest_with_v
failed_revision,
)
.expect("persist supervisor failed playtest");
let repaired_revision =
prepare_agent_runtime_project_mutation_locked(&root, "code-prototype", "child-run-2", "file.patch")
.expect("record specialist repair mutation");
let repaired_revision = prepare_agent_runtime_project_mutation_locked(
&root,
"code-prototype",
"child-run-2",
"file.patch",
)
.expect("record specialist repair mutation");
assert_eq!(repaired_revision, failed_revision + 1);
let read_arguments =
serde_json::json!({"reason": "继续读取而不验证", "input": {}}).to_string();
let read_arguments = serde_json::json!({"reason": "继续读取而不验证", "input": {}}).to_string();
let verify_arguments = serde_json::json!({
"reason": "专业 Agent 已产生新 revision,先重新执行静态验证",
"input": {"commandId": "game.static_smoke"}
@@ -10728,8 +10923,7 @@ async fn supervisor_collaboration_read_only_first_window_repairs_with_collaborat
]
})
.to_string();
let read_arguments =
serde_json::json!({"reason": "继续读取项目索引", "input": {}}).to_string();
let read_arguments = serde_json::json!({"reason": "继续读取项目索引", "input": {}}).to_string();
let delegate_function =
native_runtime_function_name("agent.delegate").expect("delegate function");
let isolated_function =
@@ -10819,7 +11013,10 @@ async fn supervisor_collaboration_read_only_first_window_repairs_with_collaborat
run_id,
AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE,
"读取后建立首批协作",
vec!["读取必要上下文".to_string(), "一次性委派两个专业 Agent".to_string()],
vec![
"读取必要上下文".to_string(),
"一次性委派两个专业 Agent".to_string(),
],
)
.expect("start supervisor runtime");
@@ -10837,7 +11034,10 @@ async fn supervisor_collaboration_read_only_first_window_repairs_with_collaborat
.expect("repair read-only initial collaboration plan")
.expect("repaired collaboration plan");
assert_eq!(plan.actions.len(), 2);
assert!(plan.actions.iter().all(|action| action.tool == "agent.delegate"));
assert!(plan
.actions
.iter()
.all(|action| action.tool == "agent.delegate"));
assert_eq!(plan.actions[0].input["agentId"], "code-prototype");
assert_eq!(plan.actions[1].input["agentId"], "quality-review");
@@ -37745,7 +37945,10 @@ async fn provider_transient_retry_autonomous_upstream_400_retries_with_bounded_b
.recv_timeout(Duration::from_millis(100))
.is_err());
assert_eq!(plan.actions.len(), 2);
assert!(plan.actions.iter().all(|action| action.tool == "agent.delegate"));
assert!(plan
.actions
.iter()
.all(|action| action.tool == "agent.delegate"));
assert!(provider_retry::read_for_run_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
@@ -3467,3 +3467,11 @@
- 处理:平台白底工具弹窗优先复用 `PlatformToolModalShell`,由共享壳读取当前 `AuthUiContext.platformTheme`,并把 `platform-theme platform-theme--<light|dark>` 挂到 portal overlay;不要用硬编码白底掩盖主题变量缺失。必须直接使用 `UnifiedModal` 的特殊场景,也要在 `overlayClassName` 显式传递当前平台主题。
- 验证:在 light / dark 主题下打开 portal 弹窗,断言 dialog 的 overlay 携带对应主题类,并在真实浏览器核对 panel 与遮罩的 computed background 均非透明。
- 关联:`src/components/project/ProjectGalleryView.tsx`、`src/components/common/PlatformToolModalShell.tsx`、`src/components/common/UnifiedModal.tsx`。
## 自主试玩失败后的修复责任不能同时落给总控和专业 Agent
- 现象:专业 Agent 已交付新 revision,Project Supervisor 的固定试玩已通过全部业务交互断言,但双视口可见性等外围门禁仍失败;下一轮 Provider 被要求直接 `file.patch`,随后又被 `orchestratorOnlyAfterDelegation` 正确拦截,格式修复耗尽后父 run 失败且没有最终回复。
- 原因:试玩失败活性门只检查“必须出现项目 mutation”,没有区分父 run 是否已经存在 durable 协作事实;它与“进入协作后 Supervisor 只委派、读取、认领和验证”的策略形成互斥合同。
- 处理:同一失败 revision 上,尚无协作事实的兼容 run 可以保留总控直接修复;已有协作事实且总控只编排时,只允许创建新的 `code-prototype` 后续修复委派,明确继承最新 `preview.validate` 诊断和 `game/index.html` 产物要求,不把它伪装成已有 repair delivery 的二次返工。专业 Agent 推进 revision 后,总控先验证新 revision,再重新试玩。
- 验证:回归测试同时覆盖“无协作时仍可直接修复”“有协作时工具目录只剩 `agent.delegate`”“专业 Agent 推进新 revision 后总控只能先复验”,并用 `supervisor-autonomous-playable-lane-defense` 真实 E2E 检查静态烟雾、桌面/移动浏览器、全部固定试玩断言、唯一 Supervisor 回复和零残留。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent.rs`、`apps/ai-game-creator-shell/src-tauri/src/tests.rs`、`apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e.mjs`。
@@ -63,6 +63,8 @@ V1.11 的受保护仓库控制目录同时包含 `.git / .agent / .agents / .cod
2026-07-17 起,Runtime V1.30 已把真实 `agc:chat / --swarm-chat` 与自主专业编排纳入同一门禁。用户只描述业务交付,不提供 Agent ID、数量、并行、工具、repair 或 Runner 配方;`project-supervisor` 必须在单个 native planning 批次自主选择至少两个不同专业 Agent 并形成真实 Provider 重叠,语义判定不满足时在同一父 Session/run 发起唯一 repair,最终以单条 `[turn.report] game-creator-swarm-turn-report.v1` 和唯一 Supervisor assistant 收束。正式 `openai_chat / gpt-5.5` 最终诊断轮已完成 `51/51` Provider lifecycle、双专业并行、2+1 delivery、2 个 Observed claim、pidfd Runner 强杀/boot 恢复、唯一正式回复和零重复/残留/泄漏,当前门禁状态为 PASS;static delivery + isolated all-join 的真实组合和 Tauri/WebView 宿主级 E2E 仍是独立后续项。
2026-07-21 补充:自主构建的最终 `preview.validate` 在当前 revision 失败后,修复责任必须继续服从 Project Supervisor 的只编排边界。父 run 尚无协作事实时可沿用总控直接修复兼容路径;一旦 durable 协作事实已建立且 `orchestratorOnlyAfterDelegation=true`,活性门不得再强迫 Supervisor 调用 `file.write / file.patch / project.patchset`,而应把原生工具目录收窄到 `agent.delegate`,向 `code-prototype` 创建 `repairOfDelegationId=null / runId=null` 的新后续修复任务,并把最新浏览器诊断和 `game/index.html` 验收产物写入合同。该后续任务用于处理最终试玩新发现的缺口,不是对已有 repair delivery 的二次返工;专业 Agent 推进到更高 revision 后,Supervisor 仍必须先取得该 revision 的静态通过凭证,再重跑固定试玩。失败 revision、专业修改、总控复验与最终试玩之间不得用伪造 mutation 衔接。
2026-07-15 起,Runtime V1.1 文档的“V1.17 单 Agent 持久计划”作为后台工具规划进度的新事实源。`submit_agent_tool_plan` 新增 nullable `planUpdate={explanation,steps[{step,status}]}`;步骤只接受 `pending / in_progress / completed`,最多 8 步且至多一个 `in_progress`。结构化计划一旦建立,legacy `plan` 只作旧协议 fallback;终态步骤必须保留,`planRevision` 只在真实变化时单调递增,工具 action 下标不得自动完成结构化步骤,存在未完成步骤时不得写最终回复或 completed。
V1.17 计划快照随 `game-creator-runtime-context-bundle.v3` 持久化,v2 在通过原身份、revision 和 verification gate 校验后从当前 Runtime state 补齐计划字段继续恢复;计划元数据本身不推进项目 revision、不改变 verification gate,也不触发项目权限确认。开发 UI 和 CLI 有界展示 revision、说明与完整 8 步;正式用户的 Supervisor 只展示完成数、当前步骤、等待对象、下一步和协作数量的紧凑摘要。恢复、same-run steer 和真实 Provider 的完整验收矩阵以 Runtime V1.17 章节为准;2026-07-16 已在当前 v5 context 上完成正式 `openai_chat / gpt-5.5` 的同 run steer + Runner 强杀恢复专项,门禁状态为 PASS。