修复自主构建验证收敛与浏览器取证
限制旧试玩失败下的重复委派并强制当前 revision 复验 收紧固定试玩控件唯一可见启用合同 为 Chrome 使用短临时目录并支持当前 run 截图别名 补齐确定性门禁与运行时决策记录
This commit is contained in:
@@ -236,6 +236,11 @@ async function runSelfTest() {
|
||||
route('code-prototype', 'repair-code-run');
|
||||
route('code-prototype', 'repair-code-run');
|
||||
route('code-prototype', 'repair-code-run', allTools, builderPlan);
|
||||
assert(
|
||||
responseFunctionNames(route('project-supervisor', 'parent-run'))[0] ===
|
||||
'runtime_tool_agent_run_status',
|
||||
'self-test-repair-claim-before-verification-invalid',
|
||||
);
|
||||
assert(
|
||||
responseFunctionNames(route('project-supervisor', 'parent-run')).join(
|
||||
',',
|
||||
@@ -243,7 +248,6 @@ async function runSelfTest() {
|
||||
'self-test-repair-verification-batch-invalid',
|
||||
);
|
||||
route('project-supervisor', 'parent-run');
|
||||
route('project-supervisor', 'parent-run');
|
||||
const stats = router.getStats();
|
||||
assert(expectedProviderStats(stats), 'self-test-provider-stats-invalid');
|
||||
|
||||
|
||||
@@ -357,19 +357,19 @@ export function createDeterministicLaneDefenseRouter({
|
||||
repairPatchCall('根据浏览器失败诊断直接修复不可见 canvas'),
|
||||
]);
|
||||
case 'verify-repair':
|
||||
parentStage = 'claim-repair';
|
||||
parentStage = 'verify-repair-after-claim';
|
||||
stats.runStatusCount += 1;
|
||||
return callsResponse(tools, [
|
||||
runStatusCall('先认领返工后的专业交付,再验证最新 revision'),
|
||||
]);
|
||||
case 'verify-repair-after-claim':
|
||||
parentStage = 'respond';
|
||||
stats.staticSmokeCount += 1;
|
||||
stats.previewValidationCount += 1;
|
||||
return callsResponse(tools, [
|
||||
staticSmokeCall('验证返工后的当前 revision'),
|
||||
previewCall('重跑完整真实试玩和双视口检查'),
|
||||
]);
|
||||
case 'claim-repair':
|
||||
parentStage = 'respond';
|
||||
stats.runStatusCount += 1;
|
||||
return callsResponse(tools, [
|
||||
runStatusCall('认领通过复验的专业返工回执'),
|
||||
]);
|
||||
case 'respond':
|
||||
parentStage = 'done';
|
||||
return callsResponse(tools, [
|
||||
|
||||
@@ -5954,8 +5954,10 @@ pub(super) async fn request_game_creator_agent_background_tool_plan_at(
|
||||
} else {
|
||||
false
|
||||
};
|
||||
match validate_agent_runtime_autonomous_plan_liveness(
|
||||
match validate_agent_runtime_autonomous_plan_liveness_at(
|
||||
root,
|
||||
agent_id,
|
||||
run_id,
|
||||
loop_index,
|
||||
project_revision,
|
||||
&verification_gate,
|
||||
@@ -6181,6 +6183,18 @@ pub(super) async fn request_game_creator_agent_background_tool_plan_at(
|
||||
AGENT_RUNTIME_AUTONOMOUS_REVERIFY_AFTER_MUTATION_LIVENESS_ERROR_PREFIX,
|
||||
)
|
||||
&& !request.function_tools.is_empty();
|
||||
let force_autonomous_supervisor_delivery_convergence = run_profile
|
||||
== AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||
&& protocol_error.starts_with(
|
||||
AGENT_RUNTIME_AUTONOMOUS_SUPERVISOR_DELIVERY_CONVERGENCE_LIVENESS_ERROR_PREFIX,
|
||||
)
|
||||
&& !request.function_tools.is_empty();
|
||||
let force_autonomous_preview_after_static = run_profile
|
||||
== AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||
&& protocol_error.starts_with(
|
||||
AGENT_RUNTIME_AUTONOMOUS_PREVIEW_AFTER_STATIC_LIVENESS_ERROR_PREFIX,
|
||||
)
|
||||
&& !request.function_tools.is_empty();
|
||||
let force_autonomous_verified_delivery = run_profile
|
||||
== AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||
&& protocol_error.starts_with(
|
||||
@@ -6219,6 +6233,8 @@ pub(super) async fn request_game_creator_agent_background_tool_plan_at(
|
||||
|| force_autonomous_failed_playtest
|
||||
|| force_autonomous_pending_verification
|
||||
|| force_autonomous_reverify_after_mutation
|
||||
|| force_autonomous_supervisor_delivery_convergence
|
||||
|| force_autonomous_preview_after_static
|
||||
|| force_autonomous_verified_delivery
|
||||
|| force_autonomous_truncated_scaffold
|
||||
|| force_autonomous_pre_mutation
|
||||
@@ -6236,6 +6252,20 @@ pub(super) 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_supervisor_delivery_convergence {
|
||||
restrict_agent_runtime_autonomous_supervisor_delivery_convergence_repair_tools(
|
||||
&mut request,
|
||||
)?;
|
||||
request.messages.push(LlmMessage::user(format!(
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n当前父 run 已有 ready 未认领回执或 3 个 active delivery,本次修复的原生工具目录只保留 agent.run_status。必须立即以 agentId=null、scope=all、delegationId=null 查询状态并原子认领 readyDelegateReceipts;不得创建第四次 agent.delegate、更新计划、读取、搜索、修改项目、重复验证或 respond_to_user。认领完成后再依据最新 project revision 重新规划验证。不要解释,不要 markdown,不要代码围栏。"
|
||||
)));
|
||||
} else if force_autonomous_preview_after_static {
|
||||
restrict_agent_runtime_autonomous_preview_after_static_repair_tools(
|
||||
&mut request,
|
||||
)?;
|
||||
request.messages.push(LlmMessage::user(format!(
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n当前 revision 已通过 game.static_smoke,本次修复的原生工具目录只保留 preview.validate。必须立即对 desktop 与 mobile 执行当前完成合同绑定的真实浏览器试玩;不得继续委派、更新计划、读取、搜索、查询状态、修改项目或 respond_to_user。不要解释,不要 markdown,不要代码围栏。"
|
||||
)));
|
||||
} else if force_autonomous_delegated_playtest_repair {
|
||||
restrict_agent_runtime_autonomous_delegated_playtest_repair_tools(
|
||||
&mut request,
|
||||
@@ -9443,7 +9473,111 @@ pub(super) fn validate_agent_runtime_autonomous_response_plan_completion(
|
||||
))
|
||||
}
|
||||
|
||||
pub(super) fn validate_agent_runtime_autonomous_plan_liveness(
|
||||
const AGENT_RUNTIME_AUTONOMOUS_SUPERVISOR_DELIVERY_CONVERGENCE_LIVENESS_ERROR_PREFIX: &str =
|
||||
"自主构建 Project Supervisor 必须先收束已有专业 Agent 委派";
|
||||
const AGENT_RUNTIME_AUTONOMOUS_PREVIEW_AFTER_STATIC_LIVENESS_ERROR_PREFIX: &str =
|
||||
"自主构建 Project Supervisor 必须试玩当前静态验证 revision";
|
||||
|
||||
fn is_agent_runtime_autonomous_supervisor_delivery_convergence_plan(
|
||||
plan: &AgentRuntimeToolPlan,
|
||||
) -> bool {
|
||||
matches!(plan.actions.as_slice(), [action]
|
||||
if action.tool.trim() == "agent.run_status"
|
||||
&& action.input.get("agentId").is_none_or(serde_json::Value::is_null)
|
||||
&& action.input.get("agent_id").is_none_or(serde_json::Value::is_null)
|
||||
&& action.input.get("scope").and_then(serde_json::Value::as_str) == Some("all")
|
||||
&& action.input.get("delegationId").is_none_or(serde_json::Value::is_null)
|
||||
&& action.input.get("delegation_id").is_none_or(serde_json::Value::is_null))
|
||||
}
|
||||
|
||||
fn is_agent_runtime_autonomous_preview_validation_plan(plan: &AgentRuntimeToolPlan) -> bool {
|
||||
matches!(plan.actions.as_slice(), [action] if action.tool.trim() == "preview.validate")
|
||||
}
|
||||
|
||||
pub(crate) fn refresh_agent_runtime_autonomous_convergence_snapshot_after_provider_at(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
run_profile: &str,
|
||||
plan: &AgentRuntimeToolPlan,
|
||||
project_revision_before: &AgentRuntimeProjectRevision,
|
||||
repository_context_fingerprint_before: &str,
|
||||
) -> Result<(AgentRuntimeProjectRevision, String), String> {
|
||||
if agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|
||||
|| run_profile != AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||
|| !is_agent_runtime_autonomous_supervisor_delivery_convergence_plan(plan)
|
||||
{
|
||||
return Ok((
|
||||
project_revision_before.clone(),
|
||||
repository_context_fingerprint_before.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
|
||||
root,
|
||||
"runtime.provider_request.rebind.convergence",
|
||||
)?;
|
||||
Ok((
|
||||
read_game_creator_agent_runtime_project_revision(root)?,
|
||||
build_repository_startup_context_at(root)?.fingerprint,
|
||||
))
|
||||
}
|
||||
|
||||
fn validate_agent_runtime_autonomous_plan_liveness_at(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
run_id: &str,
|
||||
loop_index: usize,
|
||||
project_revision: u64,
|
||||
verification_gate: &AgentRuntimeVerificationGate,
|
||||
observations: &[AgentRuntimeToolObservation],
|
||||
plan: &AgentRuntimeToolPlan,
|
||||
supervisor_requires_delegated_repair: bool,
|
||||
) -> Result<(), String> {
|
||||
if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|
||||
&& supervisor_requires_delegated_repair
|
||||
&& verification_gate
|
||||
.failed_playtest_revision
|
||||
.is_some_and(|failed_revision| failed_revision <= project_revision)
|
||||
{
|
||||
let barrier = static_delegate_completion_barrier_at(root, agent_id, run_id)?;
|
||||
let active_delegations = active_static_delegate_delivery_count_at(root, agent_id, run_id)?;
|
||||
let must_claim_or_wait = barrier.ready_unclaimed_count > 0 || active_delegations >= 3;
|
||||
if must_claim_or_wait {
|
||||
if is_agent_runtime_autonomous_supervisor_delivery_convergence_plan(plan) {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(format!(
|
||||
"{AGENT_RUNTIME_AUTONOMOUS_SUPERVISOR_DELIVERY_CONVERGENCE_LIVENESS_ERROR_PREFIX};当前父 run 的 activeDelegations={active_delegations}、waitingDelegations={}、readyUnclaimedReceipts={}。必须先只调用 agent.run_status(agentId=null、scope=all、delegationId=null)认领 ready delivery 或等待已有委派推进;不得创建第四次 agent.delegate。认领后若项目 revision 已推进,再验证当前 revision",
|
||||
barrier.waiting_count,
|
||||
barrier.ready_unclaimed_count,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let has_current_playtest_receipt = if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
|
||||
let contract = read_autonomous_completion_contract(root, agent_id, run_id)?;
|
||||
match contract.as_ref() {
|
||||
Some(contract) => read_autonomous_playtest_receipt(root, contract)?
|
||||
.is_some_and(|receipt| receipt.revision == project_revision),
|
||||
None => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
validate_agent_runtime_autonomous_plan_liveness(
|
||||
agent_id,
|
||||
loop_index,
|
||||
project_revision,
|
||||
verification_gate,
|
||||
observations,
|
||||
plan,
|
||||
supervisor_requires_delegated_repair,
|
||||
has_current_playtest_receipt,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn validate_agent_runtime_autonomous_plan_liveness(
|
||||
agent_id: &str,
|
||||
loop_index: usize,
|
||||
project_revision: u64,
|
||||
@@ -9451,6 +9585,7 @@ pub(super) fn validate_agent_runtime_autonomous_plan_liveness(
|
||||
observations: &[AgentRuntimeToolObservation],
|
||||
plan: &AgentRuntimeToolPlan,
|
||||
supervisor_requires_delegated_repair: bool,
|
||||
has_current_playtest_receipt: bool,
|
||||
) -> Result<(), String> {
|
||||
let has_mutation = plan
|
||||
.actions
|
||||
@@ -9493,6 +9628,20 @@ pub(super) fn validate_agent_runtime_autonomous_plan_liveness(
|
||||
));
|
||||
}
|
||||
}
|
||||
if !has_current_playtest_receipt
|
||||
&& verification_gate.failed_playtest_revision.is_none()
|
||||
&& verification_gate.verified_revision == Some(project_revision)
|
||||
&& verification_gate.last_verification_tool.as_deref() == Some("game.static_smoke")
|
||||
&& verification_gate.last_verification_status.as_deref()
|
||||
== Some(AGENT_RUNTIME_VERIFICATION_STATUS_PASSED)
|
||||
{
|
||||
if is_agent_runtime_autonomous_preview_validation_plan(plan) {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(format!(
|
||||
"{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")
|
||||
@@ -9859,6 +10008,46 @@ pub(super) fn restrict_agent_runtime_autonomous_failed_playtest_repair_tools(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn restrict_agent_runtime_autonomous_supervisor_delivery_convergence_repair_tools(
|
||||
request: &mut LlmRunRequest,
|
||||
) -> Result<(), String> {
|
||||
let run_status_function = native_runtime_function_name("agent.run_status")
|
||||
.ok_or_else(|| "无法生成自主试玩委派收敛工具名".to_string())?;
|
||||
request
|
||||
.function_tools
|
||||
.retain(|tool| tool.name == run_status_function);
|
||||
if request.function_tools.len() != 1 {
|
||||
return Err("自主试玩委派收敛工具目录缺少 agent.run_status".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_preview_after_static_repair_tools(
|
||||
request: &mut LlmRunRequest,
|
||||
) -> Result<(), String> {
|
||||
let preview_function = native_runtime_function_name("preview.validate")
|
||||
.ok_or_else(|| "无法生成自主当前 revision 试玩工具名".to_string())?;
|
||||
request
|
||||
.function_tools
|
||||
.retain(|tool| tool.name == preview_function);
|
||||
if request.function_tools.len() != 1 {
|
||||
return Err("自主当前 revision 试玩工具目录缺少 preview.validate".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(())
|
||||
}
|
||||
|
||||
pub(super) fn restrict_agent_runtime_autonomous_delegated_playtest_repair_tools(
|
||||
request: &mut LlmRunRequest,
|
||||
) -> Result<(), String> {
|
||||
|
||||
@@ -7613,8 +7613,8 @@ pub(super) async fn run_game_creator_agent_background_task_pass_with_context(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let planning_request_revision: AgentRuntimeProjectRevision;
|
||||
let planning_repository_context_fingerprint: String;
|
||||
let mut planning_request_revision: AgentRuntimeProjectRevision;
|
||||
let mut planning_repository_context_fingerprint: String;
|
||||
let action_start_index: usize;
|
||||
if let Some(batch) = resumed_provider_batch.as_ref() {
|
||||
let Some(first_pending) = batch.actions.first() else {
|
||||
@@ -7988,6 +7988,28 @@ pub(super) async fn run_game_creator_agent_background_task_pass_with_context(
|
||||
planning_repository_context_fingerprint = requested_plan.repository_context_fingerprint;
|
||||
let planning_mcp_catalog_fingerprint = requested_plan.mcp_catalog_fingerprint;
|
||||
plan = requested_plan.plan;
|
||||
match refresh_agent_runtime_autonomous_convergence_snapshot_after_provider_at(
|
||||
&root,
|
||||
&agent_id,
|
||||
&runtime.run_profile,
|
||||
&plan,
|
||||
&planning_request_revision,
|
||||
&planning_repository_context_fingerprint,
|
||||
) {
|
||||
Ok((revision, repository_context_fingerprint)) => {
|
||||
planning_request_revision = revision;
|
||||
planning_repository_context_fingerprint = repository_context_fingerprint;
|
||||
}
|
||||
Err(error) => {
|
||||
return fail_game_creator_agent_background_context_at(
|
||||
&root,
|
||||
&agent_id,
|
||||
&session_id,
|
||||
runtime,
|
||||
&format!("Provider 返回后刷新自主委派收敛快照失败:{error}"),
|
||||
);
|
||||
}
|
||||
}
|
||||
if runtime.run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||
&& !plan.response.trim().is_empty()
|
||||
{
|
||||
|
||||
@@ -5917,10 +5917,19 @@ pub(super) fn autonomous_playtest_contract_prompt(
|
||||
) -> &'static str {
|
||||
match scenario {
|
||||
BrowserPlaytestScenario::GenericV1 => {
|
||||
"完成合同要求 generic-v1 交互试玩。game/index.html 必须持续更新 <script id=\"playable-web-game-state\" type=\"application/json\">,JSON 固定包含 schemaVersion=playable-web-game-state.v1、单调递增 sequence、phase=ready|playing|won|lost、正整数 level;界面必须提供 data-playtest-id=\"start\" 与 data-playtest-id=\"restart\" 的真实可点击控件。start 后状态必须推进并进入 playing 或 won,restart 后必须再次推进并回到 ready 或 playing。"
|
||||
concat!(
|
||||
"完成合同要求 generic-v1 交互试玩。game/index.html 必须持续更新 <script id=\"playable-web-game-state\" type=\"application/json\">,JSON 固定包含 schemaVersion=playable-web-game-state.v1、单调递增 sequence、phase=ready|playing|won|lost、正整数 level;",
|
||||
"界面必须提供 data-playtest-id=\"start\" 与 data-playtest-id=\"restart\" 的真实可点击控件;每个固定 data-playtest-id 在对应受控试玩步骤都必须恰好匹配一个可见且启用(disabled=false)的真实可点击 HTMLElement,同一固定值不得出现在多个控件上。",
|
||||
"start 后状态必须推进并进入 playing 或 won,restart 后必须再次推进并回到 ready 或 playing。"
|
||||
)
|
||||
}
|
||||
BrowserPlaytestScenario::LaneDefenseV1 => {
|
||||
"完成合同要求 lane-defense-v1 交互试玩。game/index.html 必须持续更新 <script id=\"playable-web-game-state\" type=\"application/json\">,JSON 固定包含 schemaVersion=playable-web-game-state.v1、单调递增 sequence、phase=ready|playing|won|lost、正整数 level、selectedDefenderId、defenders 数组、enemies 数组;每个 enemy 必须含非空 id、非负 lane、会随移动变化的 position、health 与正数 maxHealth。界面必须清晰显示 Garden Guardians、Start Game、Sunflower、Peashooter、Speed Up、Next Level、Restart,并提供 data-playtest-id=\"start\"、\"defender-option\"、\"lane-cell\"、\"speed-up\"、\"next-level\"、\"restart\" 的真实可点击控件。受控试玩会依次开始、选择并放置防御单位、加速,要求敌人移动并受伤、关卡进入 won;随后 next-level 必须让 level 增加,restart 必须再次推进 sequence 并回到 ready 或 playing。"
|
||||
concat!(
|
||||
"完成合同要求 lane-defense-v1 交互试玩。game/index.html 必须持续更新 <script id=\"playable-web-game-state\" type=\"application/json\">,JSON 固定包含 schemaVersion=playable-web-game-state.v1、单调递增 sequence、phase=ready|playing|won|lost、正整数 level、selectedDefenderId、defenders 数组、enemies 数组;每个 enemy 必须含非空 id、非负 lane、会随移动变化的 position、health 与正数 maxHealth。",
|
||||
"界面必须清晰显示 Garden Guardians、Start Game、Sunflower、Peashooter、Speed Up、Next Level、Restart,并提供 data-playtest-id=\"start\"、data-playtest-id=\"defender-option\"、data-playtest-id=\"lane-cell\"、data-playtest-id=\"speed-up\"、data-playtest-id=\"next-level\"、data-playtest-id=\"restart\" 的真实可点击控件;每个固定 data-playtest-id 在对应受控试玩步骤都必须恰好匹配一个可见且启用(disabled=false)的真实可点击 HTMLElement,同一固定值不得出现在多个控件上。",
|
||||
"防御单位多选项 UI 只能给一个真实控件设置 data-playtest-id=\"defender-option\" 作为自动化入口,关卡多格 UI 只能给一个真实控件设置 data-playtest-id=\"lane-cell\" 作为自动化入口,其余选项和格子不得复用这两个固定值。",
|
||||
"受控试玩会依次开始、选择并放置防御单位、加速,要求敌人移动并受伤、关卡进入 won;随后 next-level 必须让 level 增加,restart 必须再次推进 sequence 并回到 ready 或 playing。"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6741,12 +6750,12 @@ mod autonomous_completion_contract_tests {
|
||||
);
|
||||
}
|
||||
for selector in [
|
||||
"start",
|
||||
"defender-option",
|
||||
"lane-cell",
|
||||
"speed-up",
|
||||
"next-level",
|
||||
"restart",
|
||||
"data-playtest-id=\"start\"",
|
||||
"data-playtest-id=\"defender-option\"",
|
||||
"data-playtest-id=\"lane-cell\"",
|
||||
"data-playtest-id=\"speed-up\"",
|
||||
"data-playtest-id=\"next-level\"",
|
||||
"data-playtest-id=\"restart\"",
|
||||
] {
|
||||
assert!(
|
||||
prompt.contains(selector),
|
||||
@@ -6755,6 +6764,39 @@ mod autonomous_completion_contract_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn autonomous_playtest_contract_requires_unique_visible_enabled_automation_controls() {
|
||||
for scenario in [
|
||||
BrowserPlaytestScenario::GenericV1,
|
||||
BrowserPlaytestScenario::LaneDefenseV1,
|
||||
] {
|
||||
let prompt = autonomous_playtest_contract_prompt(scenario);
|
||||
for requirement in [
|
||||
"每个固定 data-playtest-id",
|
||||
"恰好匹配一个可见且启用(disabled=false)的真实可点击 HTMLElement",
|
||||
"同一固定值不得出现在多个控件上",
|
||||
] {
|
||||
assert!(
|
||||
prompt.contains(requirement),
|
||||
"missing fixed-control requirement for {scenario:?}: {requirement}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let lane_prompt =
|
||||
autonomous_playtest_contract_prompt(BrowserPlaytestScenario::LaneDefenseV1);
|
||||
for requirement in [
|
||||
"防御单位多选项 UI 只能给一个真实控件设置 data-playtest-id=\"defender-option\" 作为自动化入口",
|
||||
"关卡多格 UI 只能给一个真实控件设置 data-playtest-id=\"lane-cell\" 作为自动化入口",
|
||||
"其余选项和格子不得复用这两个固定值",
|
||||
] {
|
||||
assert!(
|
||||
lane_prompt.contains(requirement),
|
||||
"missing single automation-entry requirement: {requirement}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delegated_autonomous_agent_inherits_parent_playtest_contract() {
|
||||
let (_temporary, root, parent_state, _contract) = autonomous_fixture(
|
||||
@@ -6984,19 +7026,28 @@ mod autonomous_completion_contract_tests {
|
||||
&superseded_failure,
|
||||
&response_plan,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
let failed_verification_gate = AgentRuntimeVerificationGate {
|
||||
verified_revision: None,
|
||||
last_verification_tool: Some("preview.validate".to_string()),
|
||||
last_verification_status: Some(AGENT_RUNTIME_VERIFICATION_STATUS_FAILED.to_string()),
|
||||
failed_playtest_revision: Some(2),
|
||||
..verification_gate
|
||||
};
|
||||
let mut latest_failure = vec![passed_preview, failed_preview];
|
||||
latest_failure.extend((0..4).map(|_| non_progress()));
|
||||
let error = validate_agent_runtime_autonomous_plan_liveness(
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
7,
|
||||
2,
|
||||
&verification_gate,
|
||||
&failed_verification_gate,
|
||||
&latest_failure,
|
||||
&response_plan,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
.expect_err("latest failed preview must still require delegated repair");
|
||||
assert!(error
|
||||
|
||||
@@ -35,7 +35,7 @@ use chromiumoxide::Page;
|
||||
use futures::StreamExt;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use tempfile::{Builder as TempDirBuilder, NamedTempFile};
|
||||
use tempfile::{Builder as TempDirBuilder, NamedTempFile, TempDir};
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::Instant;
|
||||
use url::{Host, Url};
|
||||
@@ -982,21 +982,41 @@ fn is_executable_file(path: &Path) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn browser_process_temp_root() -> PathBuf {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
PathBuf::from("/tmp")
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
std::env::temp_dir()
|
||||
}
|
||||
}
|
||||
|
||||
fn create_browser_process_temp_dir() -> Result<TempDir, String> {
|
||||
TempDirBuilder::new()
|
||||
.prefix("ga-browser-")
|
||||
.tempdir_in(browser_process_temp_root())
|
||||
.map_err(|error| format!("创建浏览器临时目录失败:{error}"))
|
||||
}
|
||||
|
||||
pub async fn validate_local_preview_in_browser(
|
||||
input: BrowserValidationInput,
|
||||
) -> Result<BrowserValidationResult, String> {
|
||||
let preview_url = validate_input(&input)?;
|
||||
prepare_evidence_root(&input.evidence_root)?;
|
||||
let browser_executable = discover_chrome_or_edge()?;
|
||||
let profile = TempDirBuilder::new()
|
||||
.prefix("genarrative-preview-browser-")
|
||||
.tempdir()
|
||||
let browser_temp = create_browser_process_temp_dir()?;
|
||||
let profile_path = browser_temp.path().join("profile");
|
||||
fs::create_dir(&profile_path)
|
||||
.map_err(|error| format!("创建浏览器临时 Profile 失败:{error}"))?;
|
||||
let browser_temp_path = browser_temp.path().to_string_lossy().into_owned();
|
||||
let proxy_bypass_list = preview_proxy_bypass_list(&preview_url);
|
||||
|
||||
let config = BrowserConfig::builder()
|
||||
.chrome_executable(&browser_executable.executable_path)
|
||||
.user_data_dir(profile.path())
|
||||
.user_data_dir(profile_path)
|
||||
.env("TMPDIR", browser_temp_path)
|
||||
.new_headless_mode()
|
||||
.enable_request_intercept()
|
||||
.disable_cache()
|
||||
@@ -1035,7 +1055,7 @@ pub async fn validate_local_preview_in_browser(
|
||||
let wait_result = tokio::time::timeout(Duration::from_secs(5), browser.wait()).await;
|
||||
handler_task.abort();
|
||||
let _ = handler_task.await;
|
||||
drop(profile);
|
||||
drop(browser_temp);
|
||||
|
||||
let mut result = validation?;
|
||||
close_result?;
|
||||
@@ -3493,6 +3513,20 @@ mod tests {
|
||||
.all(|(path, _kind)| path.is_absolute()));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn browser_process_temp_dir_keeps_chrome_singleton_socket_path_short() {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
let directory = create_browser_process_temp_dir().expect("browser process tempdir");
|
||||
let singleton_socket = directory
|
||||
.path()
|
||||
.join("com.google.Chrome.XXXXXX/SingletonSocket");
|
||||
|
||||
assert!(directory.path().starts_with("/tmp"));
|
||||
assert!(singleton_socket.as_os_str().as_bytes().len() < 108);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetch_interception_covers_all_resources_before_the_request_is_sent() {
|
||||
let params = preview_fetch_enable_params();
|
||||
|
||||
@@ -82,7 +82,12 @@ pub(crate) fn load_agent_runtime_inspection_images(
|
||||
let mut images = Vec::with_capacity(paths.len());
|
||||
let mut total_bytes = 0_u64;
|
||||
for path in paths {
|
||||
let normalized = normalize_relative_path(path.trim())?;
|
||||
let normalized = resolve_agent_runtime_inspection_path(
|
||||
root,
|
||||
&expected_agent,
|
||||
&expected_run,
|
||||
path.trim(),
|
||||
)?;
|
||||
if !unique_paths.insert(normalized.clone()) {
|
||||
return Err(format!("image.inspect 不能重复读取同一图片:{normalized}"));
|
||||
}
|
||||
@@ -104,6 +109,53 @@ pub(crate) fn load_agent_runtime_inspection_images(
|
||||
Ok(images)
|
||||
}
|
||||
|
||||
fn resolve_agent_runtime_inspection_path(
|
||||
root: &Path,
|
||||
expected_agent: &str,
|
||||
expected_run: &str,
|
||||
requested_path: &str,
|
||||
) -> Result<String, String> {
|
||||
let normalized = normalize_relative_path(requested_path)?;
|
||||
if !matches!(normalized.as_str(), "desktop.png" | "mobile.png") {
|
||||
return Ok(normalized);
|
||||
}
|
||||
|
||||
let validation_root =
|
||||
format!(".agent/runtime/browser-validations/{expected_agent}/{expected_run}");
|
||||
let validation_root_path = resolve_local_project_path(root, &validation_root)?;
|
||||
validate_agent_runtime_inspection_ancestors(
|
||||
root,
|
||||
&validation_root_path.join("revision-placeholder"),
|
||||
)?;
|
||||
let entries = fs::read_dir(&validation_root_path)
|
||||
.map_err(|error| format!("读取当前 Agent/run 浏览器截图目录失败:{error}"))?;
|
||||
let mut latest_revision = None;
|
||||
for entry in entries {
|
||||
let entry = entry.map_err(|error| format!("读取浏览器截图目录项失败:{error}"))?;
|
||||
let file_type = entry
|
||||
.file_type()
|
||||
.map_err(|error| format!("读取浏览器截图目录项类型失败:{error}"))?;
|
||||
if !file_type.is_dir() || file_type.is_symlink() {
|
||||
continue;
|
||||
}
|
||||
let Some(revision) = entry
|
||||
.file_name()
|
||||
.to_str()
|
||||
.filter(|value| !value.is_empty() && value.bytes().all(|byte| byte.is_ascii_digit()))
|
||||
.and_then(|value| value.parse::<u64>().ok())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if entry.path().join(&normalized).is_file() {
|
||||
latest_revision =
|
||||
Some(latest_revision.map_or(revision, |current: u64| current.max(revision)));
|
||||
}
|
||||
}
|
||||
let revision = latest_revision
|
||||
.ok_or_else(|| format!("当前 Agent/run 尚无可用的 {normalized} 浏览器截图"))?;
|
||||
Ok(format!("{validation_root}/{revision}/{normalized}"))
|
||||
}
|
||||
|
||||
pub(crate) fn redact_agent_runtime_image_data_urls(value: &str) -> String {
|
||||
const PREFIX: &str = "data:image/";
|
||||
let mut output = String::with_capacity(value.len());
|
||||
@@ -559,6 +611,65 @@ mod tests {
|
||||
assert!(error.contains("当前 Agent/run"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_inspect_resolves_fixed_viewport_aliases_to_latest_current_run_evidence() {
|
||||
let root = tempfile::tempdir().expect("temp root");
|
||||
let evidence_root = root
|
||||
.path()
|
||||
.join(".agent/runtime/browser-validations/project-supervisor/visual-run");
|
||||
for revision in [2_u64, 7] {
|
||||
let revision_root = evidence_root.join(revision.to_string());
|
||||
fs::create_dir_all(&revision_root).expect("revision evidence dir");
|
||||
fs::write(revision_root.join("desktop.png"), png_bytes()).expect("desktop image");
|
||||
fs::write(revision_root.join("mobile.png"), png_bytes()).expect("mobile image");
|
||||
}
|
||||
|
||||
let images = load_agent_runtime_inspection_images(
|
||||
root.path(),
|
||||
"project-supervisor",
|
||||
"visual-run",
|
||||
&["desktop.png".to_string(), "mobile.png".to_string()],
|
||||
)
|
||||
.expect("resolve current run viewport aliases");
|
||||
|
||||
assert_eq!(
|
||||
images
|
||||
.iter()
|
||||
.map(|image| image.relative_path.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
".agent/runtime/browser-validations/project-supervisor/visual-run/7/desktop.png",
|
||||
".agent/runtime/browser-validations/project-supervisor/visual-run/7/mobile.png",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_inspect_viewport_aliases_do_not_fall_back_to_another_run() {
|
||||
let root = tempfile::tempdir().expect("temp root");
|
||||
fs::create_dir_all(
|
||||
root.path()
|
||||
.join(".agent/runtime/browser-validations/project-supervisor/visual-run"),
|
||||
)
|
||||
.expect("current run evidence dir");
|
||||
let other_run = root
|
||||
.path()
|
||||
.join(".agent/runtime/browser-validations/project-supervisor/other-run/9");
|
||||
fs::create_dir_all(&other_run).expect("other run evidence dir");
|
||||
fs::write(other_run.join("desktop.png"), png_bytes()).expect("other run image");
|
||||
|
||||
let error = load_agent_runtime_inspection_images(
|
||||
root.path(),
|
||||
"project-supervisor",
|
||||
"visual-run",
|
||||
&["desktop.png".to_string()],
|
||||
)
|
||||
.err()
|
||||
.expect("alias must not resolve across runs");
|
||||
|
||||
assert!(error.contains("当前 Agent/run"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_inspect_rejects_fake_images_and_oversized_files() {
|
||||
let root = tempfile::tempdir().expect("temp root");
|
||||
|
||||
+472
@@ -1,4 +1,11 @@
|
||||
use super::super::support::*;
|
||||
use crate::{
|
||||
active_static_delegate_delivery_count_at, bind_supervisor_collaboration_policy_snapshot_at,
|
||||
create_or_read_static_delegate_delivery_at, mark_static_delegate_delivery_ready_at,
|
||||
new_static_delegate_delivery, observe_agent_runtime_run_status,
|
||||
refresh_agent_runtime_autonomous_convergence_snapshot_after_provider_at,
|
||||
static_delegate_completion_barrier_at, validate_agent_runtime_autonomous_plan_liveness,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn autonomous_game_build_profile_auto_grants_only_scoped_build_actions() {
|
||||
@@ -767,6 +774,471 @@ async fn autonomous_game_build_repairs_new_revision_after_failed_playtest_with_v
|
||||
fs::remove_dir_all(root).ok();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn autonomous_game_build_claims_ready_delivery_before_fourth_playtest_delegate() {
|
||||
let root = unique_project_path();
|
||||
init_local_game_project_at(
|
||||
&root,
|
||||
"project-autonomous-supervisor-ready-delivery-convergence",
|
||||
"自主构建总控 ready 回执收敛测试",
|
||||
)
|
||||
.expect("project init");
|
||||
let run_id = "autonomous-supervisor-ready-delivery-convergence-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,
|
||||
"收束已满的专业 Agent 委派",
|
||||
vec!["认领 ready 回执".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 failed_revision = prepare_agent_runtime_project_mutation_locked(
|
||||
&root,
|
||||
"code-prototype",
|
||||
"ready-delivery-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 planning_revision_before = read_game_creator_agent_runtime_project_revision(&root)
|
||||
.expect("read project revision before ready specialist delivery");
|
||||
let planning_repository_fingerprint_before = build_repository_startup_context_at(&root)
|
||||
.expect("build repository context before ready specialist delivery")
|
||||
.fingerprint;
|
||||
|
||||
let repaired_revision = prepare_agent_runtime_project_mutation_locked(
|
||||
&root,
|
||||
"code-prototype",
|
||||
"ready-delivery-code-run",
|
||||
"file.patch",
|
||||
)
|
||||
.expect("record specialist repair after failed playtest");
|
||||
assert_eq!(repaired_revision, failed_revision + 1);
|
||||
fs::write(
|
||||
root.join("game/index.html"),
|
||||
"<!doctype html><html><body>ready delivery revision</body></html>\n",
|
||||
)
|
||||
.expect("write specialist repair fixture");
|
||||
|
||||
let deliveries = [
|
||||
(
|
||||
"ready-delivery-action",
|
||||
"ready-delivery-code-prototype",
|
||||
"code-prototype",
|
||||
"ready-delivery-code-session",
|
||||
"ready-delivery-code-run",
|
||||
),
|
||||
(
|
||||
"waiting-delivery-action-1",
|
||||
"waiting-delivery-quality-review",
|
||||
"quality-review",
|
||||
"waiting-delivery-quality-session",
|
||||
"waiting-delivery-quality-run",
|
||||
),
|
||||
(
|
||||
"waiting-delivery-action-2",
|
||||
"waiting-delivery-design-foundation",
|
||||
"design-foundation",
|
||||
"waiting-delivery-design-session",
|
||||
"waiting-delivery-design-run",
|
||||
),
|
||||
];
|
||||
for (action_id, delegation_id, target_agent_id, target_session_id, target_run_id) in deliveries
|
||||
{
|
||||
let delivery = new_static_delegate_delivery(
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
&runtime.session_id,
|
||||
run_id,
|
||||
action_id,
|
||||
delegation_id,
|
||||
target_agent_id,
|
||||
target_session_id,
|
||||
target_run_id,
|
||||
);
|
||||
create_or_read_static_delegate_delivery_at(&root, &delivery)
|
||||
.expect("create active delivery");
|
||||
}
|
||||
mark_static_delegate_delivery_ready_at(
|
||||
&root,
|
||||
"code-prototype",
|
||||
"ready-delivery-code-session",
|
||||
"ready-delivery-code-run",
|
||||
"ready-delivery-code-prototype",
|
||||
"completed",
|
||||
"程序修复已经完成,等待父 run 认领",
|
||||
)
|
||||
.expect("mark specialist delivery ready");
|
||||
let barrier = static_delegate_completion_barrier_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
run_id,
|
||||
)
|
||||
.expect("read full delivery barrier");
|
||||
assert_eq!(barrier.waiting_count, 2);
|
||||
assert_eq!(barrier.ready_unclaimed_count, 1);
|
||||
assert_eq!(
|
||||
active_static_delegate_delivery_count_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
run_id,
|
||||
)
|
||||
.expect("count active deliveries"),
|
||||
3
|
||||
);
|
||||
|
||||
let delegate_arguments = serde_json::json!({
|
||||
"reason": "误尝试在委派容量已满时创建第四次修复委派",
|
||||
"input": {
|
||||
"agentId": "code-prototype",
|
||||
"task": "继续修复最近一次 preview.validate 的失败诊断。",
|
||||
"acceptanceCriteria": ["固定试玩场景全部通过"],
|
||||
"expectedArtifacts": ["game/index.html"],
|
||||
"repairOfDelegationId": null,
|
||||
"runId": null
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
let run_status_arguments = serde_json::json!({
|
||||
"reason": "先认领当前父 run 已 ready 的专业 Agent 回执",
|
||||
"input": {
|
||||
"agentId": null,
|
||||
"scope": "all",
|
||||
"delegationId": null
|
||||
}
|
||||
})
|
||||
.to_string();
|
||||
let preview_arguments = serde_json::json!({
|
||||
"reason": "静态验证已通过,立即试玩当前 revision",
|
||||
"input": {
|
||||
"viewports": ["desktop", "mobile"],
|
||||
"expectedText": [],
|
||||
"settleMs": 800,
|
||||
"failOnConsoleError": true,
|
||||
"playtestScenario": 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-fourth-playtest-delegate",
|
||||
&native_runtime_function_name("agent.delegate").expect("delegate function"),
|
||||
delegate_arguments.clone(),
|
||||
),
|
||||
native_agent_tool_plan_chat_response(
|
||||
"call-autonomous-supervisor-claim-ready-delivery",
|
||||
&native_runtime_function_name("agent.run_status").expect("run status function"),
|
||||
run_status_arguments,
|
||||
),
|
||||
native_agent_tool_plan_chat_response(
|
||||
"call-autonomous-supervisor-stale-delegate-after-static",
|
||||
&native_runtime_function_name("agent.delegate").expect("delegate function"),
|
||||
delegate_arguments,
|
||||
),
|
||||
native_agent_tool_plan_chat_response(
|
||||
"call-autonomous-supervisor-preview-after-static",
|
||||
&native_runtime_function_name("preview.validate").expect("preview function"),
|
||||
preview_arguments,
|
||||
),
|
||||
],
|
||||
Some(sender),
|
||||
);
|
||||
let _config_guard = write_test_local_config(format!(
|
||||
r#"{{
|
||||
"agentLlm": {{
|
||||
"project-supervisor": {{
|
||||
"apiKey": "autonomous-supervisor-ready-delivery-key",
|
||||
"baseUrl": {base_url:?},
|
||||
"model": "autonomous-supervisor-ready-delivery-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 full delivery convergence")
|
||||
.expect("ready delivery claim plan");
|
||||
assert_eq!(plan.actions.len(), 1);
|
||||
assert_eq!(plan.actions[0].tool, "agent.run_status");
|
||||
assert!(plan.actions[0]
|
||||
.input
|
||||
.get("agentId")
|
||||
.is_none_or(serde_json::Value::is_null));
|
||||
assert_eq!(
|
||||
plan.actions[0]
|
||||
.input
|
||||
.get("scope")
|
||||
.and_then(serde_json::Value::as_str),
|
||||
Some("all")
|
||||
);
|
||||
let (refreshed_revision, refreshed_repository_fingerprint) =
|
||||
refresh_agent_runtime_autonomous_convergence_snapshot_after_provider_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD,
|
||||
&plan,
|
||||
&planning_revision_before,
|
||||
&planning_repository_fingerprint_before,
|
||||
)
|
||||
.expect("refresh convergence snapshot after specialist delivery");
|
||||
assert_eq!(refreshed_revision.revision, repaired_revision);
|
||||
assert_eq!(
|
||||
refreshed_repository_fingerprint,
|
||||
build_repository_startup_context_at(&root)
|
||||
.expect("build current repository context after specialist delivery")
|
||||
.fingerprint
|
||||
);
|
||||
|
||||
receiver
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
.expect("initial fourth delegate request");
|
||||
let repair_request = receiver
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
.expect("ready delivery convergence repair request");
|
||||
assert!(repair_request.contains("activeDelegations=3"));
|
||||
assert!(repair_request.contains("readyUnclaimedReceipts=1"));
|
||||
assert!(repair_request.contains("只保留 agent.run_status"));
|
||||
assert!(repair_request.contains("不得创建第四次 agent.delegate"));
|
||||
let repair_request_json = mock_http_request_json(&repair_request);
|
||||
let repair_function_names = repair_request_json["tools"]
|
||||
.as_array()
|
||||
.expect("ready delivery convergence 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.run_status")
|
||||
.expect("run status function")
|
||||
.as_str(),])
|
||||
);
|
||||
assert!(receiver.recv_timeout(Duration::from_millis(200)).is_err());
|
||||
|
||||
let claim = observe_agent_runtime_run_status(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
run_id,
|
||||
Some("ready-delivery-convergence-claim-action"),
|
||||
&plan.actions[0].input,
|
||||
);
|
||||
assert_eq!(claim.status, "ok");
|
||||
let claim_for_split_verification = claim.clone();
|
||||
let claim_detail = claim.detail.expect("ready delivery claim detail");
|
||||
assert!(claim_detail.contains("readyDelegateReceipts"));
|
||||
assert!(claim_detail.contains("ready-delivery-code-prototype"));
|
||||
let claimed_barrier = static_delegate_completion_barrier_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
run_id,
|
||||
)
|
||||
.expect("read claimed delivery barrier");
|
||||
assert_eq!(claimed_barrier.waiting_count, 2);
|
||||
assert_eq!(claimed_barrier.ready_unclaimed_count, 0);
|
||||
assert_eq!(
|
||||
active_static_delegate_delivery_count_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
run_id,
|
||||
)
|
||||
.expect("count active deliveries after claim"),
|
||||
2
|
||||
);
|
||||
|
||||
let (current_revision, gate) = begin_agent_runtime_project_verification_locked(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
run_id,
|
||||
"game.static_smoke",
|
||||
)
|
||||
.expect("begin current revision static verification");
|
||||
finish_agent_runtime_project_verification_locked(&root, ¤t_revision, gate, true)
|
||||
.expect("finish current revision static verification");
|
||||
let current_gate = read_game_creator_agent_runtime_verification_gate(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
run_id,
|
||||
)
|
||||
.expect("read current revision static verification gate");
|
||||
assert_eq!(current_gate.failed_playtest_revision, None);
|
||||
assert_eq!(current_gate.verified_revision, Some(repaired_revision));
|
||||
let split_observations = vec![
|
||||
AgentRuntimeToolObservation {
|
||||
tool: "preview.validate".to_string(),
|
||||
status: "failed".to_string(),
|
||||
summary: "浏览器验证未通过,请根据诊断修复后重试".to_string(),
|
||||
detail: None,
|
||||
},
|
||||
AgentRuntimeToolObservation {
|
||||
tool: "agent.delegate".to_string(),
|
||||
status: "ok".to_string(),
|
||||
summary: "已创建后续专业修复委派".to_string(),
|
||||
detail: None,
|
||||
},
|
||||
claim_for_split_verification,
|
||||
AgentRuntimeToolObservation {
|
||||
tool: "command.run_limited".to_string(),
|
||||
status: "ok".to_string(),
|
||||
summary: "当前 revision 静态验证通过".to_string(),
|
||||
detail: None,
|
||||
},
|
||||
];
|
||||
let preview_plan = AgentRuntimeToolPlan {
|
||||
actions: vec![AgentRuntimeToolAction {
|
||||
tool: "preview.validate".to_string(),
|
||||
reason: Some("在下一轮对当前 revision 执行真实试玩".to_string()),
|
||||
input: serde_json::json!({
|
||||
"viewports": ["desktop", "mobile"],
|
||||
"playtestScenario": null
|
||||
}),
|
||||
}],
|
||||
..AgentRuntimeToolPlan::default()
|
||||
};
|
||||
assert!(validate_agent_runtime_autonomous_plan_liveness(
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
31,
|
||||
repaired_revision,
|
||||
¤t_gate,
|
||||
&split_observations,
|
||||
&preview_plan,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
let stale_delegate_plan = AgentRuntimeToolPlan {
|
||||
actions: vec![AgentRuntimeToolAction {
|
||||
tool: "agent.delegate".to_string(),
|
||||
reason: Some("错误地沿用旧试玩失败继续委派".to_string()),
|
||||
input: serde_json::json!({
|
||||
"agentId": "code-prototype",
|
||||
"task": "继续修复旧试玩失败",
|
||||
"acceptanceCriteria": ["固定试玩场景全部通过"],
|
||||
"expectedArtifacts": ["game/index.html"],
|
||||
"repairOfDelegationId": null,
|
||||
"runId": null
|
||||
}),
|
||||
}],
|
||||
..AgentRuntimeToolPlan::default()
|
||||
};
|
||||
let stale_delegate_error = validate_agent_runtime_autonomous_plan_liveness(
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
31,
|
||||
repaired_revision,
|
||||
¤t_gate,
|
||||
&split_observations,
|
||||
&stale_delegate_plan,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
.expect_err("static-verified revision must reject another stale delegate");
|
||||
assert!(stale_delegate_error
|
||||
.starts_with("自主构建 Project Supervisor 必须试玩当前静态验证 revision"));
|
||||
|
||||
let repaired_preview_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,
|
||||
&split_observations,
|
||||
31,
|
||||
0,
|
||||
)
|
||||
.await
|
||||
.expect("repair stale delegation after current static verification")
|
||||
.expect("forced current revision preview plan");
|
||||
assert_eq!(repaired_preview_plan.actions.len(), 1);
|
||||
assert_eq!(repaired_preview_plan.actions[0].tool, "preview.validate");
|
||||
|
||||
receiver
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
.expect("stale delegate after static verification request");
|
||||
let preview_repair_request = receiver
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
.expect("forced preview after static verification request");
|
||||
assert!(preview_repair_request.contains("只保留 preview.validate"));
|
||||
let preview_repair_json = mock_http_request_json(&preview_repair_request);
|
||||
let preview_repair_function_names = preview_repair_json["tools"]
|
||||
.as_array()
|
||||
.expect("preview 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!(
|
||||
preview_repair_function_names,
|
||||
BTreeSet::from([native_runtime_function_name("preview.validate")
|
||||
.expect("preview 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_oversized_native_source_payload() {
|
||||
let root = unique_project_path();
|
||||
|
||||
@@ -5211,3 +5211,18 @@
|
||||
- liveness 语义:observation 兜底只判断最新一条 `preview.validate`。最新成功结果会取代同一 run 更早的失败 observation,不能在成功试玩后因历史失败再次强制委派;最新结果仍为失败时继续阻断收束。持久 verification gate 的 `failedPlaytestRevision` 仍是优先事实源,不因本次修正放宽。
|
||||
- 验收证据:本轮确定性 wrapper 与正式子 suite 同轮 PASS。Provider `17` 次 planning、异常 `0`;项目 revision `0 -> 2`,`game.static_smoke` 与桌面/移动浏览器通过,`lane-defense-v1` 固定试玩 `37/37`;三份委派回执全部认领,唯一 Supervisor assistant,pending、sidecar、reconciliation、重复、密钥/正文/路径泄漏均为 `0`,隔离 Runner、AppData、配置和项目全部清理。
|
||||
- 边界:本门禁证明本地确定性 OpenAI-compatible 路由可以驱动完整正式链路,不代表任何外部 Provider 已通过。外部 Provider 的鉴权、网络与模型行为必须用独立同轮 E2E 记录,不能与本结果拼接。
|
||||
|
||||
## 2026-07-22 AI 游戏创作自主可玩构建按 revision 收敛
|
||||
|
||||
- 背景:外部 Provider 已把项目从失败试玩所在 revision 推进到更高 revision,且专业 delivery 已 ready,但父 Supervisor 仍被旧 `failedPlaytestRevision` 导向新的 `agent.delegate`。同一父 run 已有 3 个 active/ready delivery 时,第四次委派只会稳定命中容量上限,已通过的最新项目也无法进入最终收束。
|
||||
- 决策:当父验证门仍有旧试玩失败,且存在 ready 未认领回执或 active delivery 已达 3 个时,liveness repair 只开放 `agent.run_status`。动作必须使用 `agentId=null / scope=all / delegationId=null` 原子认领当前父 run 的 ready delivery;不得创建第四次委派。认领后由现有 revision 门禁要求当前 revision 先取得静态验证,再由父 Supervisor 执行 `preview.validate`。
|
||||
- 边界:当前 revision 自身产生的新试玩失败、没有 ready 回执且委派容量未满时,仍可进入新的专业修复委派;本决策不取消真实返工,只禁止旧 revision 失败越过已有交付重复派工。`agent.run_status` 只用于 ready 认领和当前状态收束,不恢复模型轮询式等待。
|
||||
- 试玩合同:`generic-v1` 与 `lane-defense-v1` 的每个固定 `data-playtest-id` 在对应动作发生时必须恰好匹配一个可见、启用且真实可点击的 HTMLElement。多选项和多格 UI 只能各指定一个自动化入口,缺失、重复、隐藏或 disabled 均失败关闭,浏览器 validator 不做“取第一个”或文本定位回退。
|
||||
- 回归:构造“父试玩失败 -> 专业 Agent 推进新 revision -> 2 个 dispatched + 1 个 ready delivery”,断言格式修复目录只含 `agent.run_status`、ready 回执被认领、active 数从 3 降到 2,且既有新 revision 复验回归继续通过。完整确定性与外部 Provider E2E 仍需分别同轮验收,不能拼接证据。
|
||||
|
||||
## 2026-07-22 自主试玩浏览器与截图检查使用短路径和固定别名
|
||||
|
||||
- 背景:真实外部 E2E 的隔离 AppData 会形成较长 `TMPDIR`。Chrome 134 在该目录下继续创建 `com.google.Chrome.../SingletonSocket` 时超过 Unix socket 路径上限并以 status `134` 退出;另一次真实轮次已通过桌面/移动试玩,但模型只按提示调用 `image.inspect(paths=["desktop.png","mobile.png"])`,无法知道带 Agent、run 和 revision 的持久截图路径。
|
||||
- 决策:Unix 浏览器子进程统一在 `/tmp/ga-browser-*` 下创建临时根目录和 Profile,并显式把该短目录作为子进程 `TMPDIR`;证据仍写入项目内既有受控目录。`image.inspect` 仅把精确 basename `desktop.png / mobile.png` 解析为当前 Agent、当前 run 下数字最大的已有 revision 截图,不跨 Agent、run 回退,也不改变显式项目相对路径语义。
|
||||
- 安全边界:截图别名仍经过项目根、祖先 symlink、普通文件、图片格式和总字节上限校验。没有当前 run 截图时失败关闭,不能为提高成功率搜索全项目或复用旧 run 证据。
|
||||
- 验收状态:短路径真实 Chrome smoke、截图别名定向测试、Rust 串行全量 `1139 passed / 5 ignored / 0 failed` 和确定性自主构建 E2E 均通过。外部 Provider 后续轮次在已完成真实浏览器试玩后出现单次非重试 Provider lifecycle 失败,整轮仍为 FAIL;当前不能据此宣称外部 Provider 完整 PASS。
|
||||
|
||||
@@ -3475,3 +3475,25 @@
|
||||
- 处理:同一失败 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`。
|
||||
|
||||
## 旧 revision 试玩失败不能越过 ready delivery 触发重复委派
|
||||
|
||||
- 现象:父 Supervisor 在较早 revision 的试玩失败后已经收到专业 Agent 推进的新 revision,且 delivery 处于 ready;但下一轮仍按旧失败创建第四次 `agent.delegate`,随后持续命中 `activeDelegations=3`,即使最新项目已通过浏览器试玩也不能结束。
|
||||
- 原因:试玩失败 liveness 先执行“协作后必须委派”,没有先检查同一父 run 的 ready 未认领回执和 active delivery 容量;生成合同又只要求提供固定 `data-playtest-id`,没有明确每个值必须唯一、可见和启用。
|
||||
- 处理:旧失败仍存在但 ready 回执可认领或 active delivery 已满时,只允许 `agent.run_status` 原子认领/观察既有委派;认领后验证当前 revision,再由父 run 重跑固定试玩。只有当前 revision 自身的新失败且没有待收束交付时才创建后续专业修复。固定试玩控件必须唯一匹配、可见、启用且真实可点击。
|
||||
- 验证:构造 `failed preview@旧 revision + ready delivery@新 revision`,断言顺序为 `agent.run_status -> verification@当前 revision -> preview.validate@当前 revision`,委派总数不超过 3;分别以缺失、重复、隐藏和 disabled 的固定控件验证浏览器失败关闭。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol.rs`、`apps/ai-game-creator-shell/src-tauri/src/tests/runtime_actions/planning_strategy/autonomous_build.rs`。
|
||||
|
||||
## 隔离 AppData 的长 TMPDIR 会让 Chrome SingletonSocket 超限
|
||||
|
||||
- 现象:静态检查已通过,但 `preview.validate` 在 Chrome 启动阶段以 status `134` 失败;minidump 中是 `process_singleton_posix.cc` 的 `Socket path too long`,页面逻辑和截图探针均未开始。
|
||||
- 原因:真实 E2E 为隔离 Runner 创建的 AppData 路径较长,Chrome 又在 `TMPDIR` 下拼接 SingletonSocket 名称,最终超过 Unix domain socket 路径上限。
|
||||
- 处理:浏览器进程使用 `/tmp/ga-browser-*` 短临时根,同时把 Profile 放在该根目录并显式传入子进程 `TMPDIR`;项目证据目录不变。用真实长 `TMPDIR` 环境运行 Chrome smoke,不能只测临时目录字符串长度。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/browser.rs`。
|
||||
|
||||
## image.inspect 的桌面和移动截图别名必须绑定当前 run
|
||||
|
||||
- 现象:真实桌面/移动试玩已经通过并生成截图,Supervisor 随后反复调用 `image.inspect` 的 `desktop.png / mobile.png`,但工具只接受完整项目相对路径,最终耗尽 planning 循环。
|
||||
- 原因:Provider 能看到固定截图名,却看不到持久证据路径中的 Agent、run 和 revision;让模型猜完整内部路径既不稳定,也会扩大私有路径暴露面。
|
||||
- 处理:只为精确 basename 提供受控别名,解析到当前 Agent、当前 run 下最新数字 revision;显式路径继续按原规则处理。严禁跨 run 搜索“最近截图”,否则旧轮次成功证据会污染当前验收。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/image_inspect.rs`。
|
||||
|
||||
@@ -64,7 +64,7 @@ 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-22 补充:自主构建的最终 `preview.validate` 在当前 revision 失败后,修复责任必须继续服从 Project Supervisor 的只编排边界。父 run 尚无协作事实时可沿用总控直接修复兼容路径;一旦 durable 协作事实已建立且 `orchestratorOnlyAfterDelegation=true`,活性门不得再强迫 Supervisor 调用 `file.write / file.patch / project.patchset`。没有 ready 回执且委派容量未满时,可向 `code-prototype` 创建 `repairOfDelegationId=null / runId=null` 的新后续修复任务,并把最新浏览器诊断和 `game/index.html` 验收产物写入合同;已有 ready 未认领回执或 active delivery 已达 3 个时,原生工具目录必须只保留 `agent.run_status`,先原子认领既有交付,不得创建第四次委派。专业 Agent 推进到更高 revision 后,固定顺序为“认领 ready delivery -> 取得当前 revision 的静态通过凭证 -> 父 Supervisor 重跑固定试玩”。每个固定 `data-playtest-id` 在对应动作发生时必须恰好匹配一个可见、启用且真实可点击的 HTMLElement;缺失、重复、隐藏或 disabled 都失败关闭。失败 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。
|
||||
|
||||
@@ -669,5 +669,7 @@ game-project/
|
||||
- V1.43 的确定性门禁必须覆盖 base handoff 与 repair handoff 两个 lifecycle-completed 前断点,关闭 mock Provider 后恢复零网络、原 requestId 唯一闭合、repair/protocol audit 幂等、唯一 assistant/completed/committed stream及终局零 sidecar。独立非默认真实门禁 `supervisor-swarm-tool-plan-handoff-runner-kill` 已实现并完成 Shell/Root 两级注册:它使用 sentinel-owned sibling AppData 与 metadata-only zero-fault proxy,以每轮随机 capability 严格绑定 project/Agent/run/实际 request slot;只有 tool-plan handoff 原子落盘并回读一致、同一实际 requestId lifecycle 尚未 `completed` 时才 ACK,随后通过 pidfd `SIGKILL` 强杀 suite 自有 Runner。恢复必须证明同一 requestId 唯一闭合且 `networkReplayCount=0`、protocol/repair audit 幂等、handoff 与 durable batch plan fingerprint 对应、恢复消费前 action/pending/delivery/claim 等副作用为 `0`,并在终局把 sidecar、重复记录、临时 capability/Runner/AppData 资源及公共正文、凭据、URL、项目/正式配置路径泄漏全部清零。2026-07-20 的真实外部 Provider 单轮已到达并通过 checkpoint,但随后专业 Agent 连续连接失败使整轮 FAIL;另一独立轮首批工具数不满足 fixture,也未通过。两轮不得拼接,当前仍无该 suite 的完整外部 PASS。Provider 成功到 handoff 原子落盘回读前的 unknown-result 和手动 context-compaction 仍不在本切片承诺内。
|
||||
- V1.43 当前确定性实现已通过本轮 `tool_plan_handoff_ 44/44`、Supervisor collaboration 相关过滤 `55/55`、权威返工合同 `1/1`,以及 Tauri/Rust 串行全量 `1054 passed / 4 ignored / 0 failed`;Linux `cargo check --tests` 与 `x86_64-pc-windows-gnu cargo check --tests` 均通过。E2E self-test、typecheck、变更脚本 ESLint、encoding 与 `git diff --check` 通过;默认并发全量只作竞态诊断,不替代串行门禁。Supervisor 真实 E2E 报告已把 `toolPlanHandoffSidecarCount` 纳入终局残留。handoff 跨平台存储使用 Unix 固定目录句柄、目录 `flock`、exchange/quarantine 与 Windows 相对父句柄、句柄枚举、独占 temp,不再根据 PID 推断写入方是否存活;主动忽略锁的同 UID 进程仍属于宿主 OS 信任边界。
|
||||
- 2026-07-21 起,同一 Runtime 文档的“V1.44 自主可玩塔防确定性真实门禁”增加独立 loopback OpenAI Chat Provider 和 wrapper 命令。Provider 只返回原生 function calls,不直接修改项目、不伪造工具 observation;wrapper 在仓库外创建带 sentinel 的临时配置,复用正式 `supervisor-autonomous-playable-lane-defense` suite,并在终局停止 Provider、删除配置和 disposable 项目。
|
||||
- V1.44 固定验证两份首轮并行专业委派、只读验收回复因 revision 更新而重新规划、程序 Agent 写入并通过静态自检、首轮真实浏览器因隐藏 canvas 失败、Supervisor 直接修改被 orchestrator-only 策略拒绝、后续程序委派产生新 revision、同一 planning 批次完成 `game.static_smoke + preview.validate`、成功后再用 `agent.run_status` 认领后续回执,最后只由 Supervisor 回复。试玩 liveness 只以最新一条 `preview.validate` observation 为准:后续成功会取代历史失败,最新失败仍继续强制专业返工;持久 `failedPlaytestRevision` 门禁保持不变。
|
||||
- V1.44 固定验证两份首轮并行专业委派、只读验收回复因 revision 更新而重新规划、程序 Agent 写入并通过静态自检、首轮真实浏览器因隐藏 canvas 失败、Supervisor 直接修改被 orchestrator-only 策略拒绝,以及后续程序委派产生新 revision。若旧失败仍在父验证门且后续 delivery 已 ready,必须先用 `agent.run_status` 认领回执,再对当前 revision 完成 `game.static_smoke + preview.validate`,最后只由 Supervisor 回复;已有 3 个 active/ready delivery 时不得创建第四次委派。试玩 liveness 只以当前 revision 可归属的最新 `preview.validate` 结果收束:新 revision 的成功会取代历史失败,当前 revision 最新失败仍继续强制专业返工;每个固定 `data-playtest-id` 必须唯一匹配一个可见、启用且真实可点击的 HTMLElement。
|
||||
- 本轮 V1.44 wrapper 与正式子 suite 均为 **PASS**:Provider 共 `17` 次 planning、异常请求 `0`;项目从 revision `0` 推进到 `2`,最终 `game/index.html` 为 `4924` 字节;`lane-defense-v1` 的植物选择、放置、敌人移动与受伤、胜利、下一关和重开共 `37/37` 断言通过,桌面与移动浏览器验证通过;三份专业回执全部认领,Supervisor assistant 唯一,pending、confirmation、user-input、provider batch/retry/handoff、tool-plan handoff、finalization journal、reconciliation、重复和泄漏计数均为 `0`,隔离 Runner、AppData、配置和项目已清理。该确定性 loopback PASS 不能替代外部 Provider 可用性验收;外部路由仍须单独形成同轮完整 PASS。
|
||||
- 2026-07-22 的外部 Provider 排障补齐两个宿主边界:Chrome 子进程必须使用 `/tmp/ga-browser-*` 短 `TMPDIR`,避免隔离 AppData 令 SingletonSocket 超过 Unix 路径上限;`image.inspect` 的 `desktop.png / mobile.png` 固定别名只解析当前 Agent/run 最新 revision 截图,不能跨 run 回退。对应真实 Chrome smoke、Rust 串行全量 `1139/1139` 和确定性正式 E2E 已通过。
|
||||
- 外部 Provider 仍无可拼接的完整 PASS:一轮因 Chrome status `134` 失败,一轮在真实浏览器 `37/37` 后因截图 basename 无法解析耗尽循环;别名修复后的独立轮次已再次推进到真实浏览器通过和截图检查阶段,但 `58` 个 Provider lifecycle 中出现 `1` 个非重试失败,父 turn 终态为 failed。后续必须以新的独立完整轮次证明生成、静态检查、真实浏览器、截图审阅、唯一 Supervisor 回复和零残留全部同轮通过。
|
||||
|
||||
Reference in New Issue
Block a user