修复游戏创作编排恢复与进度可观测性
提取并固化 Supervisor 工作流与任务图约束 修复 game-chat 计划收敛、当前 Run 写入归属和素材完成门 补强 parent wake 持久恢复、幂等投影与 deferred 竞态 改进 Agent 进度消息时间戳、等待态和滚动可见性 补齐 Rust、App Surface 回归及项目文档
This commit is contained in:
@@ -106,6 +106,7 @@ pub(crate) use project_gates::{
|
|||||||
};
|
};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) use project_gates::{
|
pub(crate) use project_gates::{
|
||||||
|
ensure_current_autonomous_ready_child_mutation_at_locked,
|
||||||
supervisor_collaboration_policy_completion_blocker_for_test_at,
|
supervisor_collaboration_policy_completion_blocker_for_test_at,
|
||||||
supervisor_orchestrator_mutation_block_after_dispatch_for_test,
|
supervisor_orchestrator_mutation_block_after_dispatch_for_test,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,12 +50,17 @@ pub(crate) fn append_agent_runtime_tool_call_record(
|
|||||||
action: &AgentRuntimeToolAction,
|
action: &AgentRuntimeToolAction,
|
||||||
observation: &AgentRuntimeToolObservation,
|
observation: &AgentRuntimeToolObservation,
|
||||||
action_id: Option<&str>,
|
action_id: Option<&str>,
|
||||||
|
action_fingerprint: Option<&str>,
|
||||||
) {
|
) {
|
||||||
let record = AgentRuntimeToolCallRecord {
|
let record = AgentRuntimeToolCallRecord {
|
||||||
action_id: action_id.map(ToString::to_string),
|
action_id: action_id.map(ToString::to_string),
|
||||||
tool: observation.tool.clone(),
|
tool: observation.tool.clone(),
|
||||||
status: observation.status.clone(),
|
status: observation.status.clone(),
|
||||||
action_fingerprint: Some(agent_runtime_tool_action_fingerprint(action, task)),
|
action_fingerprint: Some(
|
||||||
|
action_fingerprint
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.unwrap_or_else(|| agent_runtime_tool_action_fingerprint(action, task)),
|
||||||
|
),
|
||||||
input_summary: agent_runtime_tool_action_input_summary(root, action),
|
input_summary: agent_runtime_tool_action_input_summary(root, action),
|
||||||
reason: action
|
reason: action
|
||||||
.reason
|
.reason
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_
|
|||||||
false,
|
false,
|
||||||
|| observe_agent_runtime_memory(root, agent_id, &action.input),
|
|| observe_agent_runtime_memory(root, agent_id, &action.input),
|
||||||
),
|
),
|
||||||
"memory.write" => observe_agent_runtime_memory_write(root, agent_id, &action.input),
|
"memory.write" => observe_agent_runtime_memory_write(root, agent_id, run_id, &action.input),
|
||||||
"conversation.read" => observe_agent_runtime_project_snapshot_with_lock(
|
"conversation.read" => observe_agent_runtime_project_snapshot_with_lock(
|
||||||
root,
|
root,
|
||||||
agent_id,
|
agent_id,
|
||||||
@@ -285,8 +285,8 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_
|
|||||||
false,
|
false,
|
||||||
|| observe_agent_runtime_task_list(root, agent_id, run_id),
|
|| observe_agent_runtime_task_list(root, agent_id, run_id),
|
||||||
),
|
),
|
||||||
"task.create" => observe_agent_runtime_task_create(root, agent_id, &action.input),
|
"task.create" => observe_agent_runtime_task_create(root, agent_id, run_id, &action.input),
|
||||||
"task.update" => observe_agent_runtime_task_update(root, agent_id, &action.input),
|
"task.update" => observe_agent_runtime_task_update(root, agent_id, run_id, &action.input),
|
||||||
"command.exec" => {
|
"command.exec" => {
|
||||||
observe_agent_runtime_command_exec(
|
observe_agent_runtime_command_exec(
|
||||||
root,
|
root,
|
||||||
@@ -386,7 +386,9 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
"blackboard.write" => observe_agent_runtime_blackboard_write(root, agent_id, &action.input),
|
"blackboard.write" => {
|
||||||
|
observe_agent_runtime_blackboard_write(root, agent_id, run_id, &action.input)
|
||||||
|
}
|
||||||
"agent.message" => {
|
"agent.message" => {
|
||||||
observe_agent_runtime_agent_message(root, agent_id, run_id, &action.input)
|
observe_agent_runtime_agent_message(root, agent_id, run_id, &action.input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ pub(in crate::agent) fn persist_game_creator_agent_user_input_wait_at(
|
|||||||
&pending.action,
|
&pending.action,
|
||||||
&waiting_observation,
|
&waiting_observation,
|
||||||
Some(&pending.action_id),
|
Some(&pending.action_id),
|
||||||
|
Some(&pending.action_fingerprint),
|
||||||
);
|
);
|
||||||
runtime.pending_tool_action = Some(pending.summary());
|
runtime.pending_tool_action = Some(pending.summary());
|
||||||
runtime.status = "waiting-for-user-input".to_string();
|
runtime.status = "waiting-for-user-input".to_string();
|
||||||
|
|||||||
+53
-35
@@ -536,34 +536,13 @@ pub(in crate::agent) fn autonomous_manifest_dag_in_progress_at(
|
|||||||
root: &Path,
|
root: &Path,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
let manifest = read_manifest_for_project(root)?;
|
let manifest = read_manifest_for_project(root)?;
|
||||||
let source = read_game_creator_agent_runtime_at(root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID)
|
let current_root = current_autonomous_game_build_root_task_at(root)?.ok_or_else(|| {
|
||||||
.ok()
|
"无法解析当前自主构建根 Run 的可信 source,拒绝按 GUI 完整 DAG 回退".to_string()
|
||||||
.filter(|runtime| {
|
})?;
|
||||||
runtime.state.run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
if !autonomous_game_build_root_task_is_active(¤t_root) {
|
||||||
&& agent_runtime_supervisor_source_is_trusted(&runtime.state.source)
|
return Ok(false);
|
||||||
})
|
}
|
||||||
.map(|runtime| runtime.state.source)
|
let seed_task_ids = autonomous_manifest_seed_tasks_for_source(¤t_root.source)
|
||||||
.or_else(|| {
|
|
||||||
let path = game_creator_agent_runtime_task_path(
|
|
||||||
root,
|
|
||||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
|
||||||
);
|
|
||||||
read_all_game_creator_agent_runtime_tasks(&path)
|
|
||||||
.ok()
|
|
||||||
.map(latest_game_creator_agent_runtime_tasks)
|
|
||||||
.and_then(|records| {
|
|
||||||
records.into_iter().rev().find_map(|record| {
|
|
||||||
(record.run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
|
||||||
&& record.parent_run_id.is_none()
|
|
||||||
&& agent_runtime_supervisor_source_is_trusted(&record.source))
|
|
||||||
.then_some(record.source)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.ok_or_else(|| {
|
|
||||||
"无法解析当前自主构建根 Run 的可信 source,拒绝按 GUI 完整 DAG 回退".to_string()
|
|
||||||
})?;
|
|
||||||
let seed_task_ids = autonomous_manifest_seed_tasks_for_source(&source)
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|task| task.id)
|
.map(|task| task.id)
|
||||||
.collect::<BTreeSet<_>>();
|
.collect::<BTreeSet<_>>();
|
||||||
@@ -572,19 +551,58 @@ pub(in crate::agent) fn autonomous_manifest_dag_in_progress_at(
|
|||||||
.iter()
|
.iter()
|
||||||
.filter(|task| seed_task_ids.contains(&task.id))
|
.filter(|task| seed_task_ids.contains(&task.id))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let started = seed_tasks
|
|
||||||
.iter()
|
|
||||||
.any(|task| task.status != GameCreationAppTaskStatus::Pending);
|
|
||||||
let running = seed_tasks
|
let running = seed_tasks
|
||||||
.iter()
|
.iter()
|
||||||
.any(|task| task.status == GameCreationAppTaskStatus::Running);
|
.any(|task| task.status == GameCreationAppTaskStatus::Running);
|
||||||
let completed = seed_tasks
|
|
||||||
.iter()
|
|
||||||
.all(|task| task.status == GameCreationAppTaskStatus::Completed);
|
|
||||||
let failed = seed_tasks
|
let failed = seed_tasks
|
||||||
.iter()
|
.iter()
|
||||||
.any(|task| task.status == GameCreationAppTaskStatus::Failed);
|
.any(|task| task.status == GameCreationAppTaskStatus::Failed);
|
||||||
Ok(started && running && !completed && !failed)
|
let active_child = autonomous_manifest_parent_has_active_ready_task_at(
|
||||||
|
root,
|
||||||
|
¤t_root.run_id,
|
||||||
|
&seed_task_ids,
|
||||||
|
)?;
|
||||||
|
Ok((running || active_child) && !failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autonomous_manifest_parent_has_active_ready_task_at(
|
||||||
|
root: &Path,
|
||||||
|
parent_run_id: &str,
|
||||||
|
seed_task_ids: &BTreeSet<String>,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
for task_id in seed_task_ids {
|
||||||
|
let records =
|
||||||
|
latest_game_creator_agent_runtime_tasks(read_all_game_creator_agent_runtime_tasks(
|
||||||
|
&game_creator_agent_runtime_task_path(root, task_id),
|
||||||
|
)?);
|
||||||
|
for record in records {
|
||||||
|
if record.source != "agent-ready-task-scheduler"
|
||||||
|
|| record.parent_agent_id.as_deref()
|
||||||
|
!= Some(GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID)
|
||||||
|
|| record.parent_run_id.as_deref() != Some(parent_run_id)
|
||||||
|
|| game_creator_agent_runtime_terminal_status(&record).is_some()
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if record.run_id != autonomous_manifest_ready_task_run_id(parent_run_id, task_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"当前自主构建父 Run 的活跃 child runId 不符合确定性绑定:taskId={task_id}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let state = agent_runtime_state_from_task_record(&record);
|
||||||
|
let binding = autonomous_manifest_ready_task_parent_binding_for_state_at(root, &state)?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
format!("当前自主构建父 Run 的活跃 child 缺少父绑定:taskId={task_id}")
|
||||||
|
})?;
|
||||||
|
if binding.root_run_id != parent_run_id {
|
||||||
|
return Err(format!(
|
||||||
|
"当前自主构建父 Run 的活跃 child rootRunId 不一致:taskId={task_id}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn validate_agent_runtime_autonomous_plan_liveness(
|
pub(crate) fn validate_agent_runtime_autonomous_plan_liveness(
|
||||||
|
|||||||
@@ -776,6 +776,7 @@ pub(in crate::agent) fn project_game_creator_agent_runtime_parallel_read_batch(
|
|||||||
&pending.action,
|
&pending.action,
|
||||||
&observation,
|
&observation,
|
||||||
Some(&pending.action_id),
|
Some(&pending.action_id),
|
||||||
|
Some(&pending.action_fingerprint),
|
||||||
);
|
);
|
||||||
runtime.pending_tool_action = None;
|
runtime.pending_tool_action = None;
|
||||||
runtime.status = "running".to_string();
|
runtime.status = "running".to_string();
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ pub(crate) fn prepare_agent_runtime_project_mutation_locked(
|
|||||||
blocker.detail.unwrap_or_default()
|
blocker.detail.unwrap_or_default()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
ensure_current_autonomous_ready_child_mutation_at_locked(root, agent_id, run_id)?;
|
||||||
let mut revision = read_game_creator_agent_runtime_project_revision(root)?;
|
let mut revision = read_game_creator_agent_runtime_project_revision(root)?;
|
||||||
let mut gate = read_game_creator_agent_runtime_verification_gate(root, agent_id, run_id)?;
|
let mut gate = read_game_creator_agent_runtime_verification_gate(root, agent_id, run_id)?;
|
||||||
let next_revision = revision
|
let next_revision = revision
|
||||||
@@ -176,6 +177,159 @@ pub(crate) fn prepare_agent_runtime_project_mutation_locked(
|
|||||||
Ok(next_revision)
|
Ok(next_revision)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn ensure_current_autonomous_ready_child_mutation_at_locked(
|
||||||
|
root: &Path,
|
||||||
|
agent_id: &str,
|
||||||
|
run_id: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let normalized_agent_id = match normalize_game_creator_runtime_agent_id(agent_id) {
|
||||||
|
Ok(agent_id) => agent_id,
|
||||||
|
Err(_) => return Ok(()),
|
||||||
|
};
|
||||||
|
let binding =
|
||||||
|
read_game_creator_agent_runtime_run_profile_binding(root, &normalized_agent_id, run_id)?;
|
||||||
|
let Some(binding) = binding else {
|
||||||
|
let task = read_latest_game_creator_agent_runtime_task_by_run_id(
|
||||||
|
root,
|
||||||
|
&normalized_agent_id,
|
||||||
|
run_id,
|
||||||
|
)?;
|
||||||
|
if task
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|task| task.run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD)
|
||||||
|
{
|
||||||
|
return Err("autonomous Run 项目修改缺少 Run Profile binding,已失败关闭".to_string());
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
if binding.profile != AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if binding.agent_id != normalized_agent_id
|
||||||
|
|| binding.run_id != run_id
|
||||||
|
|| binding.root_agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|
||||||
|
{
|
||||||
|
return Err("autonomous Run 项目修改的 Run Profile 绑定身份不一致".to_string());
|
||||||
|
}
|
||||||
|
let task =
|
||||||
|
read_latest_game_creator_agent_runtime_task_by_run_id(root, &normalized_agent_id, run_id)?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
"autonomous Run 项目修改缺少 durable task journal,已失败关闭".to_string()
|
||||||
|
})?;
|
||||||
|
if task.agent_id != binding.agent_id
|
||||||
|
|| task.run_id != binding.run_id
|
||||||
|
|| task.source != binding.source
|
||||||
|
|| task.run_profile != binding.profile
|
||||||
|
|| task.run_profile_binding_fingerprint != binding.binding_fingerprint
|
||||||
|
|| task.parent_agent_id != binding.parent_agent_id
|
||||||
|
|| task.parent_run_id != binding.parent_run_id
|
||||||
|
{
|
||||||
|
return Err("autonomous Run 项目修改的 durable task journal 与绑定不一致".to_string());
|
||||||
|
}
|
||||||
|
let is_root = binding.agent_id == binding.root_agent_id
|
||||||
|
&& binding.run_id == binding.root_run_id
|
||||||
|
&& binding.parent_agent_id.is_none()
|
||||||
|
&& binding.parent_run_id.is_none();
|
||||||
|
if is_root {
|
||||||
|
if task.parent_agent_id.is_some()
|
||||||
|
|| task.parent_run_id.is_some()
|
||||||
|
|| !agent_runtime_supervisor_source_is_trusted(&task.source)
|
||||||
|
{
|
||||||
|
return Err("autonomous 根 Run 项目修改的 durable identity 不一致".to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let parent_agent_id = binding
|
||||||
|
.parent_agent_id
|
||||||
|
.as_deref()
|
||||||
|
.ok_or_else(|| "autonomous 派生 Run 项目修改缺少 parentAgentId".to_string())?;
|
||||||
|
let parent_run_id = binding
|
||||||
|
.parent_run_id
|
||||||
|
.as_deref()
|
||||||
|
.ok_or_else(|| "autonomous 派生 Run 项目修改缺少 parentRunId".to_string())?;
|
||||||
|
let parent_binding = read_game_creator_agent_runtime_run_profile_binding(
|
||||||
|
root,
|
||||||
|
parent_agent_id,
|
||||||
|
parent_run_id,
|
||||||
|
)?
|
||||||
|
.ok_or_else(|| "autonomous 派生 Run 项目修改缺少父 Run Profile binding".to_string())?;
|
||||||
|
if binding.parent_binding_fingerprint.as_deref()
|
||||||
|
!= Some(parent_binding.binding_fingerprint.as_str())
|
||||||
|
|| parent_binding.profile != AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||||
|
|| parent_binding.root_agent_id != binding.root_agent_id
|
||||||
|
|| parent_binding.root_run_id != binding.root_run_id
|
||||||
|
{
|
||||||
|
return Err(
|
||||||
|
"autonomous 派生 Run 项目修改的父 binding 或 root identity 不一致".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if binding.source == "agent-ready-task-scheduler" {
|
||||||
|
let state = agent_runtime_state_from_task_record(&task);
|
||||||
|
let ready_binding =
|
||||||
|
autonomous_manifest_ready_task_parent_binding_for_state_at(root, &state)?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
"autonomous ready-task 项目修改缺少确定性父 Run 绑定".to_string()
|
||||||
|
})?;
|
||||||
|
if ready_binding != binding
|
||||||
|
|| state.run_id
|
||||||
|
!= autonomous_manifest_ready_task_run_id(
|
||||||
|
&binding.root_run_id,
|
||||||
|
&normalized_agent_id,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return Err("autonomous ready-task 项目修改的确定性父子身份不一致".to_string());
|
||||||
|
}
|
||||||
|
} else if binding.source == "agent-delegate"
|
||||||
|
&& task
|
||||||
|
.delegation_id
|
||||||
|
.as_deref()
|
||||||
|
.is_none_or(|delegation_id| delegation_id.trim().is_empty())
|
||||||
|
{
|
||||||
|
return Err("autonomous agent-delegate 项目修改缺少 delegationId".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if task.status != "running" || game_creator_agent_runtime_terminal_status(&task).is_some() {
|
||||||
|
return Err("autonomous Run 项目修改要求当前 durable task 仍为 running".to_string());
|
||||||
|
}
|
||||||
|
let current_root = current_autonomous_game_build_root_task_at(root)?
|
||||||
|
.ok_or_else(|| "autonomous Run 项目修改时当前根 Run 已不存在".to_string())?;
|
||||||
|
if current_root.run_id != binding.root_run_id {
|
||||||
|
return Err(format!(
|
||||||
|
"autonomous Run 已被更新根 Run 取代:currentRunId={}",
|
||||||
|
current_root.run_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let current_root_binding = read_game_creator_agent_runtime_run_profile_binding(
|
||||||
|
root,
|
||||||
|
¤t_root.agent_id,
|
||||||
|
¤t_root.run_id,
|
||||||
|
)?
|
||||||
|
.ok_or_else(|| "autonomous Run 当前根缺少 Run Profile binding".to_string())?;
|
||||||
|
if current_root.agent_id != binding.root_agent_id
|
||||||
|
|| current_root.source != current_root_binding.source
|
||||||
|
|| current_root.run_profile != AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||||
|
|| current_root.parent_agent_id.is_some()
|
||||||
|
|| current_root.parent_run_id.is_some()
|
||||||
|
|| current_root.delegation_id.is_some()
|
||||||
|
|| current_root_binding.agent_id != binding.root_agent_id
|
||||||
|
|| current_root_binding.run_id != binding.root_run_id
|
||||||
|
|| current_root_binding.root_agent_id != current_root_binding.agent_id
|
||||||
|
|| current_root_binding.root_run_id != current_root_binding.run_id
|
||||||
|
|| current_root_binding.parent_agent_id.is_some()
|
||||||
|
|| current_root_binding.parent_run_id.is_some()
|
||||||
|
|| current_root_binding.binding_fingerprint != current_root.run_profile_binding_fingerprint
|
||||||
|
|| (is_root && current_root_binding.binding_fingerprint != binding.binding_fingerprint)
|
||||||
|
{
|
||||||
|
return Err("autonomous Run 当前根 journal 与 binding 不一致".to_string());
|
||||||
|
}
|
||||||
|
if !autonomous_game_build_root_task_is_active(¤t_root) {
|
||||||
|
return Err(format!(
|
||||||
|
"autonomous Run 当前根已不再活跃:status={} phase={}",
|
||||||
|
current_root.status, current_root.phase
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn begin_agent_runtime_project_verification_locked(
|
pub(crate) fn begin_agent_runtime_project_verification_locked(
|
||||||
root: &Path,
|
root: &Path,
|
||||||
agent_id: &str,
|
agent_id: &str,
|
||||||
|
|||||||
+2
@@ -689,6 +689,7 @@ pub(in crate::agent) fn persist_game_creator_agent_runtime_provider_batch_waitin
|
|||||||
&pending.action,
|
&pending.action,
|
||||||
observation,
|
observation,
|
||||||
Some(&pending.action_id),
|
Some(&pending.action_id),
|
||||||
|
Some(&pending.action_fingerprint),
|
||||||
);
|
);
|
||||||
complete_agent_runtime_active_plan_step(
|
complete_agent_runtime_active_plan_step(
|
||||||
runtime,
|
runtime,
|
||||||
@@ -788,6 +789,7 @@ pub(in crate::agent) fn project_game_creator_agent_runtime_provider_batch_abort(
|
|||||||
&pending.action,
|
&pending.action,
|
||||||
&observation,
|
&observation,
|
||||||
Some(&pending.action_id),
|
Some(&pending.action_id),
|
||||||
|
Some(&pending.action_fingerprint),
|
||||||
);
|
);
|
||||||
complete_agent_runtime_active_plan_step(runtime, "failed", &observation_summary);
|
complete_agent_runtime_active_plan_step(runtime, "failed", &observation_summary);
|
||||||
runtime.pending_tool_action = None;
|
runtime.pending_tool_action = None;
|
||||||
|
|||||||
@@ -101,6 +101,8 @@ pub(crate) const AGENT_RUNTIME_ISOLATED_JOIN_SOURCE: &str = "agent-isolated-join
|
|||||||
pub(crate) const AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE: &str = "project-supervisor-gui";
|
pub(crate) const AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE: &str = "project-supervisor-gui";
|
||||||
pub(crate) const AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE: &str = "project-supervisor-cli";
|
pub(crate) const AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE: &str = "project-supervisor-cli";
|
||||||
pub(crate) const AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE: &str = "project-supervisor-game-chat";
|
pub(crate) const AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE: &str = "project-supervisor-game-chat";
|
||||||
|
pub(super) const GAME_CHAT_FIXED_TASK_GRAPH_STALLED_ERROR: &str =
|
||||||
|
"game-chat 首版固定任务图无法继续推进,拒绝回退到普通 Provider 协作波";
|
||||||
|
|
||||||
pub(crate) fn agent_runtime_supervisor_source_is_trusted(source: &str) -> bool {
|
pub(crate) fn agent_runtime_supervisor_source_is_trusted(source: &str) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
@@ -277,8 +279,12 @@ pub(crate) use provider_recovery::{
|
|||||||
};
|
};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) use provider_recovery::{
|
pub(crate) use provider_recovery::{
|
||||||
|
drive_waiting_autonomous_manifest_parent_wake_budget_for_test,
|
||||||
ensure_waiting_provider_retry_records_for_test,
|
ensure_waiting_provider_retry_records_for_test,
|
||||||
|
mark_autonomous_manifest_parent_wake_needs_reconciliation_for_test,
|
||||||
|
prepare_waiting_autonomous_manifest_parent_for_test,
|
||||||
probe_static_delegate_parent_wake_singleflight_coalescing,
|
probe_static_delegate_parent_wake_singleflight_coalescing,
|
||||||
|
repair_autonomous_manifest_parent_wake_reconciliation_projection_for_test,
|
||||||
};
|
};
|
||||||
pub(crate) use recovery_scan::{
|
pub(crate) use recovery_scan::{
|
||||||
cleanup_game_creator_agent_runtime_completed_finalizations_at,
|
cleanup_game_creator_agent_runtime_completed_finalizations_at,
|
||||||
@@ -293,10 +299,12 @@ pub(crate) use task_queue::{
|
|||||||
run_game_creator_agent_background_task_with_context,
|
run_game_creator_agent_background_task_with_context,
|
||||||
spawn_next_game_creator_agent_background_task_drain,
|
spawn_next_game_creator_agent_background_task_drain,
|
||||||
spawn_next_game_creator_agent_background_task_drain_with_lock,
|
spawn_next_game_creator_agent_background_task_drain_with_lock,
|
||||||
|
spawn_started_game_creator_agent_background_task_drain_with_lock,
|
||||||
};
|
};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) use task_start::start_game_creator_agent_background_task_with_session_lane_hook_at;
|
pub(crate) use task_start::start_game_creator_agent_background_task_with_session_lane_hook_at;
|
||||||
pub(crate) use task_start::{
|
pub(crate) use task_start::{
|
||||||
|
autonomous_game_build_root_task_is_active, current_autonomous_game_build_root_task_at,
|
||||||
notify_external_agent_runner_after_background_task_enqueue,
|
notify_external_agent_runner_after_background_task_enqueue,
|
||||||
project_autonomous_manifest_ready_task_terminal_at,
|
project_autonomous_manifest_ready_task_terminal_at,
|
||||||
schedule_autonomous_game_build_ready_tasks_at, schedule_game_creator_agent_ready_tasks_at,
|
schedule_autonomous_game_build_ready_tasks_at, schedule_game_creator_agent_ready_tasks_at,
|
||||||
|
|||||||
@@ -584,8 +584,14 @@ pub(in crate::agent) fn read_game_creator_agent_runtime_with_session_filter_at(
|
|||||||
}
|
}
|
||||||
let recent_events =
|
let recent_events =
|
||||||
read_recent_game_creator_agent_runtime_events_for_session(&event_path, session_id)?;
|
read_recent_game_creator_agent_runtime_events_for_session(&event_path, session_id)?;
|
||||||
let task_snapshot =
|
let task_snapshot = read_game_creator_agent_runtime_task_snapshot_for_session(
|
||||||
read_game_creator_agent_runtime_task_snapshot_for_session(&task_path, session_id)?;
|
&task_path,
|
||||||
|
session_id,
|
||||||
|
(!state.run_id.trim().is_empty()).then_some(state.run_id.as_str()),
|
||||||
|
)?;
|
||||||
|
if state.started_at == 0 {
|
||||||
|
state.started_at = task_snapshot.run_started_at.unwrap_or(state.updated_at);
|
||||||
|
}
|
||||||
state.task_queue = task_snapshot.task_queue.clone();
|
state.task_queue = task_snapshot.task_queue.clone();
|
||||||
let response_stream =
|
let response_stream =
|
||||||
visible_game_creator_agent_runtime_response_stream_at(root, &state).unwrap_or(None);
|
visible_game_creator_agent_runtime_response_stream_at(root, &state).unwrap_or(None);
|
||||||
|
|||||||
+359
-1
@@ -12,6 +12,7 @@ pub(crate) const GAME_CHAT_FIRST_PLAYABLE_HARD_BUDGET_ERROR_PREFIX: &str =
|
|||||||
"game-chat-first-playable-hard-budget-exhausted";
|
"game-chat-first-playable-hard-budget-exhausted";
|
||||||
|
|
||||||
const FALLBACK_THEME_MARKER: &str = "__GAME_CHAT_THEME__";
|
const FALLBACK_THEME_MARKER: &str = "__GAME_CHAT_THEME__";
|
||||||
|
pub(super) const GAME_CHAT_CODE_COMPLETION_REPAIR_STEP: &str = "修复 Runtime 完成门诊断并重新验证";
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub(crate) struct GameChatFastPathBudget {
|
pub(crate) struct GameChatFastPathBudget {
|
||||||
@@ -313,6 +314,134 @@ fn game_chat_fast_path_has_art_manifest(root: &Path) -> bool {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn game_chat_english_words(task: &str) -> Vec<&str> {
|
||||||
|
task.split(|character: char| !character.is_ascii_alphanumeric())
|
||||||
|
.filter(|word| !word.is_empty())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn game_chat_english_words_contain_phrase(words: &[&str], phrase: &[&str]) -> bool {
|
||||||
|
words
|
||||||
|
.windows(phrase.len())
|
||||||
|
.any(|candidate| candidate == phrase)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn game_chat_english_application_is_negated(words: &[&str], application_index: usize) -> bool {
|
||||||
|
let prefix = &words[application_index.saturating_sub(4)..application_index];
|
||||||
|
prefix
|
||||||
|
.iter()
|
||||||
|
.any(|word| matches!(*word, "not" | "never" | "dont"))
|
||||||
|
|| game_chat_english_words_contain_phrase(prefix, &["don", "t"])
|
||||||
|
|| ["refuse", "refuses", "refused"]
|
||||||
|
.iter()
|
||||||
|
.any(|refusal| prefix.ends_with(&[*refusal]) || prefix.ends_with(&[*refusal, "to"]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn game_chat_chinese_application_is_negated(clause: &str, application_index: usize) -> bool {
|
||||||
|
let prefix = clause[..application_index].trim_end();
|
||||||
|
[
|
||||||
|
"不要", "不", "别", "勿", "请勿", "禁止", "拒绝", "避免", "无需", "无须", "不能", "不可",
|
||||||
|
"不得", "不应",
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.any(|negation| prefix.ends_with(negation))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn game_chat_chinese_clause_requests_existing_art_application(clause: &str) -> bool {
|
||||||
|
let names_art = ["美术资源", "美术素材", "已有素材", "现有素材"]
|
||||||
|
.iter()
|
||||||
|
.any(|marker| clause.contains(marker));
|
||||||
|
if !names_art {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let explicitly_names_existing_art = ["已有美术", "现有美术", "已有素材", "现有素材"]
|
||||||
|
.iter()
|
||||||
|
.any(|marker| clause.contains(marker));
|
||||||
|
let requests_new_art = [
|
||||||
|
"全新美术",
|
||||||
|
"新的美术",
|
||||||
|
"新美术",
|
||||||
|
"全新素材",
|
||||||
|
"新的素材",
|
||||||
|
"新素材",
|
||||||
|
"重新生成美术",
|
||||||
|
"重做美术",
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.any(|marker| clause.contains(marker));
|
||||||
|
if requests_new_art && !explicitly_names_existing_art {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
["替换", "换成", "接入", "使用", "应用", "复用"]
|
||||||
|
.iter()
|
||||||
|
.any(|application| {
|
||||||
|
clause.match_indices(application).any(|(index, _)| {
|
||||||
|
// “换成” is also a suffix of “替换成”; the latter must be
|
||||||
|
// judged once at the beginning of the complete action.
|
||||||
|
!(*application == "换成" && clause[..index].ends_with('替'))
|
||||||
|
&& !game_chat_chinese_application_is_negated(clause, index)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn game_chat_explicit_existing_art_reuse_intent(task: &str) -> bool {
|
||||||
|
let normalized = task.trim().to_ascii_lowercase();
|
||||||
|
let english_words = game_chat_english_words(&normalized);
|
||||||
|
let requests_existing_art_in_chinese = normalized
|
||||||
|
.split(|character: char| {
|
||||||
|
matches!(
|
||||||
|
character,
|
||||||
|
',' | '。' | ';' | ';' | ',' | '.' | '!' | '!' | '?' | '?' | '\n' | '\r'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.any(game_chat_chinese_clause_requests_existing_art_application);
|
||||||
|
let requests_new_art_in_english = [["new", "art"], ["fresh", "art"], ["regenerate", "art"]]
|
||||||
|
.iter()
|
||||||
|
.any(|phrase| game_chat_english_words_contain_phrase(&english_words, phrase));
|
||||||
|
let requests_application_in_english = english_words.iter().enumerate().any(|(index, word)| {
|
||||||
|
matches!(*word, "replace" | "use" | "apply" | "reuse")
|
||||||
|
&& !game_chat_english_application_is_negated(&english_words, index)
|
||||||
|
});
|
||||||
|
let names_art_in_english = [
|
||||||
|
&["art", "asset"][..],
|
||||||
|
&["art", "assets"][..],
|
||||||
|
&["spritesheet"][..],
|
||||||
|
&["spritesheets"][..],
|
||||||
|
&["sprite", "sheet"][..],
|
||||||
|
&["sprite", "sheets"][..],
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.any(|phrase| game_chat_english_words_contain_phrase(&english_words, phrase));
|
||||||
|
requests_existing_art_in_chinese
|
||||||
|
|| (!requests_new_art_in_english && requests_application_in_english && names_art_in_english)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::agent) fn game_chat_existing_art_reuse_refinement_intent_at(
|
||||||
|
root: &Path,
|
||||||
|
task: &str,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
if !game_chat_explicit_existing_art_reuse_intent(task)
|
||||||
|
|| game_chat_fallback_targets_initial_placeholder(root)?
|
||||||
|
{
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::agent) fn game_chat_existing_art_reuse_refinement_is_valid_at(
|
||||||
|
root: &Path,
|
||||||
|
task: &str,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
if !game_chat_existing_art_reuse_refinement_intent_at(root, task)? {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
Ok(game_chat_fast_path_has_visual_asset(root, "art-director")
|
||||||
|
&& game_chat_fast_path_has_visual_asset(root, "art-asset-plan")
|
||||||
|
&& game_chat_fast_path_has_art_manifest(root))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn game_chat_fast_path_scheduled_art_contract_repair_is_authorized_at(
|
pub(crate) fn game_chat_fast_path_scheduled_art_contract_repair_is_authorized_at(
|
||||||
root: &Path,
|
root: &Path,
|
||||||
agent_id: &str,
|
agent_id: &str,
|
||||||
@@ -727,6 +856,165 @@ fn game_chat_fast_path_verified_delivery_plan(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn game_chat_fast_path_completion_repair_plan(
|
||||||
|
runtime: &AgentRuntimeState,
|
||||||
|
blocker: &AgentRuntimeToolObservation,
|
||||||
|
) -> Option<AgentRuntimeToolPlan> {
|
||||||
|
if !agent_runtime_has_structured_plan(runtime)
|
||||||
|
|| runtime
|
||||||
|
.plan_steps
|
||||||
|
.iter()
|
||||||
|
.any(|step| step.title == GAME_CHAT_CODE_COMPLETION_REPAIR_STEP)
|
||||||
|
|| runtime
|
||||||
|
.plan_steps
|
||||||
|
.iter()
|
||||||
|
.any(|step| step.status == AGENT_RUNTIME_PLAN_STATUS_FAILED)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let first_non_terminal_index = runtime.plan_steps.iter().position(|step| {
|
||||||
|
step.status != AGENT_RUNTIME_PLAN_STATUS_COMPLETED
|
||||||
|
&& step.status != AGENT_RUNTIME_PLAN_STATUS_FAILED
|
||||||
|
});
|
||||||
|
if first_non_terminal_index.is_none()
|
||||||
|
&& runtime.plan_steps.len() >= AGENT_RUNTIME_PLAN_STEP_LIMIT
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut repair_inserted = false;
|
||||||
|
let mut steps = runtime
|
||||||
|
.plan_steps
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, step)| {
|
||||||
|
if Some(index) == first_non_terminal_index {
|
||||||
|
repair_inserted = true;
|
||||||
|
return AgentRuntimePlanUpdateStep {
|
||||||
|
step: GAME_CHAT_CODE_COMPLETION_REPAIR_STEP.to_string(),
|
||||||
|
status: AGENT_RUNTIME_PLAN_STATUS_IN_PROGRESS.to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
AgentRuntimePlanUpdateStep {
|
||||||
|
step: step.title.clone(),
|
||||||
|
status: if step.status == AGENT_RUNTIME_PLAN_STATUS_COMPLETED {
|
||||||
|
AGENT_RUNTIME_PLAN_STATUS_COMPLETED
|
||||||
|
} else {
|
||||||
|
AGENT_RUNTIME_PLAN_STATUS_PENDING
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !repair_inserted {
|
||||||
|
steps.push(AgentRuntimePlanUpdateStep {
|
||||||
|
step: GAME_CHAT_CODE_COMPLETION_REPAIR_STEP.to_string(),
|
||||||
|
status: AGENT_RUNTIME_PLAN_STATUS_IN_PROGRESS.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(AgentRuntimeToolPlan {
|
||||||
|
thinking_summary: "静态检查已通过,但 Runtime 完成门仍有明确诊断;重新打开修复计划并交给 Code Agent 处理。"
|
||||||
|
.to_string(),
|
||||||
|
plan_update: Some(AgentRuntimePlanUpdate {
|
||||||
|
explanation: format!(
|
||||||
|
"{}:{}",
|
||||||
|
blocker.summary,
|
||||||
|
blocker.detail.as_deref().unwrap_or("请按完成门诊断继续修复")
|
||||||
|
),
|
||||||
|
steps,
|
||||||
|
}),
|
||||||
|
plan: Vec::new(),
|
||||||
|
actions: Vec::new(),
|
||||||
|
response: String::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn game_chat_fast_path_external_repair_observation_at(
|
||||||
|
root: &Path,
|
||||||
|
runtime: &AgentRuntimeState,
|
||||||
|
) -> Option<AgentRuntimeToolObservation> {
|
||||||
|
if runtime.agent_id != "code-prototype"
|
||||||
|
|| !agent_runtime_has_structured_plan(runtime)
|
||||||
|
|| runtime
|
||||||
|
.plan_steps
|
||||||
|
.iter()
|
||||||
|
.any(|step| step.status == AGENT_RUNTIME_PLAN_STATUS_FAILED)
|
||||||
|
|| (!runtime.plan_steps.iter().any(|step| {
|
||||||
|
step.title == GAME_CHAT_CODE_COMPLETION_REPAIR_STEP
|
||||||
|
|| step
|
||||||
|
.title
|
||||||
|
.starts_with(&format!("{GAME_CHAT_CODE_COMPLETION_REPAIR_STEP}("))
|
||||||
|
}) && runtime.plan_steps.iter().any(|step| {
|
||||||
|
step.status != AGENT_RUNTIME_PLAN_STATUS_COMPLETED
|
||||||
|
&& step.status != AGENT_RUNTIME_PLAN_STATUS_FAILED
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
autonomous_game_build_completion_blocker_at_locked(root, runtime).map(|mut blocker| {
|
||||||
|
blocker.summary = format!(
|
||||||
|
"{};结构化计划窗口已终态,进入外部 repair lane,只执行读取、实际 mutation 与重新验证",
|
||||||
|
blocker.summary
|
||||||
|
);
|
||||||
|
blocker
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn game_chat_fast_path_current_run_owns_mutation(
|
||||||
|
root: &Path,
|
||||||
|
runtime: &AgentRuntimeState,
|
||||||
|
gate: &AgentRuntimeVerificationGate,
|
||||||
|
revision: u64,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
let Some(tool) = gate
|
||||||
|
.last_mutation_tool
|
||||||
|
.as_deref()
|
||||||
|
.filter(|_| gate.mutation_revision == Some(revision))
|
||||||
|
else {
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
let Some(last_mutation_call) = runtime
|
||||||
|
.recent_tool_calls
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.find(|call| call.tool == tool)
|
||||||
|
else {
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
let (Some(action_id), Some(action_fingerprint)) = (
|
||||||
|
last_mutation_call.action_id.as_deref(),
|
||||||
|
last_mutation_call.action_fingerprint.as_deref(),
|
||||||
|
) else {
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
if last_mutation_call.status != "ok"
|
||||||
|
|| !is_valid_agent_runtime_action_id(action_id)
|
||||||
|
|| !is_valid_agent_runtime_action_fingerprint(action_fingerprint)
|
||||||
|
{
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
let (records, _) =
|
||||||
|
read_agent_db_records_bounded(root, AGENT_RUNTIME_ACTION_HISTORY_MAX_DB_BYTES)?;
|
||||||
|
Ok(records.iter().rev().any(|record| {
|
||||||
|
record.get("recordType").and_then(serde_json::Value::as_str)
|
||||||
|
== Some(AGENT_RUNTIME_ACTION_RECEIPT_RECORD_TYPE)
|
||||||
|
&& record.get("agentId").and_then(serde_json::Value::as_str)
|
||||||
|
== Some(runtime.agent_id.as_str())
|
||||||
|
&& record.get("taskId").and_then(serde_json::Value::as_str)
|
||||||
|
== Some(runtime.task_id.as_str())
|
||||||
|
&& record.get("sessionId").and_then(serde_json::Value::as_str)
|
||||||
|
== Some(runtime.session_id.as_str())
|
||||||
|
&& record.get("runId").and_then(serde_json::Value::as_str)
|
||||||
|
== Some(runtime.run_id.as_str())
|
||||||
|
&& record.get("actionId").and_then(serde_json::Value::as_str) == Some(action_id)
|
||||||
|
&& record
|
||||||
|
.get("actionFingerprint")
|
||||||
|
.and_then(serde_json::Value::as_str)
|
||||||
|
== Some(action_fingerprint)
|
||||||
|
&& record.get("tool").and_then(serde_json::Value::as_str) == Some(tool)
|
||||||
|
&& record.get("status").and_then(serde_json::Value::as_str) == Some("ok")
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
fn game_chat_fast_path_current_revision_is_verified(
|
fn game_chat_fast_path_current_revision_is_verified(
|
||||||
root: &Path,
|
root: &Path,
|
||||||
runtime: &AgentRuntimeState,
|
runtime: &AgentRuntimeState,
|
||||||
@@ -898,13 +1186,29 @@ pub(crate) fn game_chat_fast_path_plan_at(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"code-prototype" => {
|
"code-prototype" => {
|
||||||
|
if agent_runtime_has_structured_plan(runtime)
|
||||||
|
&& runtime
|
||||||
|
.plan_steps
|
||||||
|
.iter()
|
||||||
|
.any(|step| step.status == AGENT_RUNTIME_PLAN_STATUS_FAILED)
|
||||||
|
{
|
||||||
|
return Err(
|
||||||
|
"game-chat code-prototype 结构化计划已包含 failed 步骤,拒绝继续空转"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
let revision = read_game_creator_agent_runtime_project_revision(root)?;
|
let revision = read_game_creator_agent_runtime_project_revision(root)?;
|
||||||
let gate = read_game_creator_agent_runtime_verification_gate(
|
let gate = read_game_creator_agent_runtime_verification_gate(
|
||||||
root,
|
root,
|
||||||
&runtime.agent_id,
|
&runtime.agent_id,
|
||||||
&runtime.run_id,
|
&runtime.run_id,
|
||||||
)?;
|
)?;
|
||||||
let owns_current_mutation = gate.mutation_revision == Some(revision.revision);
|
let owns_current_mutation = game_chat_fast_path_current_run_owns_mutation(
|
||||||
|
root,
|
||||||
|
runtime,
|
||||||
|
&gate,
|
||||||
|
revision.revision,
|
||||||
|
)?;
|
||||||
let current_revision_verified = owns_current_mutation
|
let current_revision_verified = owns_current_mutation
|
||||||
&& gate.verified_revision == Some(revision.revision)
|
&& gate.verified_revision == Some(revision.revision)
|
||||||
&& gate.last_verification_status.as_deref()
|
&& gate.last_verification_status.as_deref()
|
||||||
@@ -914,6 +1218,13 @@ pub(crate) fn game_chat_fast_path_plan_at(
|
|||||||
== Some(AGENT_RUNTIME_VERIFICATION_STATUS_FAILED);
|
== Some(AGENT_RUNTIME_VERIFICATION_STATUS_FAILED);
|
||||||
|
|
||||||
if current_revision_verified {
|
if current_revision_verified {
|
||||||
|
if let Some(blocker) =
|
||||||
|
autonomous_game_build_completion_blocker_at_locked(root, runtime)
|
||||||
|
{
|
||||||
|
return Ok(game_chat_fast_path_completion_repair_plan(
|
||||||
|
runtime, &blocker,
|
||||||
|
));
|
||||||
|
}
|
||||||
return Ok(Some(game_chat_fast_path_verified_delivery_plan(
|
return Ok(Some(game_chat_fast_path_verified_delivery_plan(
|
||||||
runtime,
|
runtime,
|
||||||
"首个可玩版本代码已生成并通过静态自检。",
|
"首个可玩版本代码已生成并通过静态自检。",
|
||||||
@@ -1650,6 +1961,53 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn existing_art_reuse_intent_requires_whole_english_action_words_and_rejects_negation() {
|
||||||
|
let accepted = [
|
||||||
|
"Use existing art assets in the current game.",
|
||||||
|
"Please REUSE the existing art assets.",
|
||||||
|
"Replace placeholders with current art assets.",
|
||||||
|
"Apply the existing spritesheet to the UI.",
|
||||||
|
"Reuse the sprite-sheet for the falling blocks.",
|
||||||
|
"Use existing art assets; do not generate new ones.",
|
||||||
|
"使用现有素材,不要重新生成。",
|
||||||
|
"请复用已有美术资源。",
|
||||||
|
"不要重新生成美术,继续接入现有素材。",
|
||||||
|
];
|
||||||
|
for task in accepted {
|
||||||
|
assert!(
|
||||||
|
game_chat_explicit_existing_art_reuse_intent(task),
|
||||||
|
"expected existing-art reuse intent: {task}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rejected = [
|
||||||
|
"Do not use existing art assets.",
|
||||||
|
"Do NOT use the existing art assets.",
|
||||||
|
"Don't reuse existing art assets.",
|
||||||
|
"Never apply the existing spritesheet.",
|
||||||
|
"Do not replace the UI with existing art assets.",
|
||||||
|
"We refuse to use existing art assets.",
|
||||||
|
"Refuse art assets.",
|
||||||
|
"Misuse art assets.",
|
||||||
|
"These are useful art assets.",
|
||||||
|
"Discuss art assets because they exist.",
|
||||||
|
"Create new art assets.",
|
||||||
|
"Regenerate art assets.",
|
||||||
|
"不要复用已有素材。",
|
||||||
|
"不要接入现有素材。",
|
||||||
|
"不应用已有美术资源。",
|
||||||
|
"别替换成现有素材。",
|
||||||
|
"不要使用现有素材,改为重做美术。",
|
||||||
|
];
|
||||||
|
for task in rejected {
|
||||||
|
assert!(
|
||||||
|
!game_chat_explicit_existing_art_reuse_intent(task),
|
||||||
|
"expected no existing-art reuse intent: {task}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn art_slice_completion_validation_rejects_tampering_and_duplicate_pixels() {
|
fn art_slice_completion_validation_rejects_tampering_and_duplicate_pixels() {
|
||||||
let temporary = tempfile::tempdir().expect("create slice validation project");
|
let temporary = tempfile::tempdir().expect("create slice validation project");
|
||||||
|
|||||||
@@ -891,6 +891,7 @@ pub(in crate::agent) fn advance_game_creator_agent_runtime_provider_batch_gate(
|
|||||||
&next_pending.action,
|
&next_pending.action,
|
||||||
&observation,
|
&observation,
|
||||||
Some(&next_pending.action_id),
|
Some(&next_pending.action_id),
|
||||||
|
Some(&next_pending.action_fingerprint),
|
||||||
);
|
);
|
||||||
activate_agent_runtime_plan_step(
|
activate_agent_runtime_plan_step(
|
||||||
runtime,
|
runtime,
|
||||||
|
|||||||
@@ -112,9 +112,7 @@ async fn request_game_creator_agent_tool_plan_with_game_chat_budget_at(
|
|||||||
.map(RequestedAgentRuntimeToolPlanOutcome::Ready);
|
.map(RequestedAgentRuntimeToolPlanOutcome::Ready);
|
||||||
}
|
}
|
||||||
if runtime.agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
|
if runtime.agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
|
||||||
return Err(
|
return Err(GAME_CHAT_FIXED_TASK_GRAPH_STALLED_ERROR.to_string());
|
||||||
"game-chat 首版固定任务图无法继续推进,拒绝回退到普通 Provider 协作波".to_string(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
let Some(timeout) = game_chat_fast_path_provider_timeout(&budget) else {
|
let Some(timeout) = game_chat_fast_path_provider_timeout(&budget) else {
|
||||||
if runtime.agent_id == "code-prototype" {
|
if runtime.agent_id == "code-prototype" {
|
||||||
@@ -127,13 +125,17 @@ async fn request_game_creator_agent_tool_plan_with_game_chat_budget_at(
|
|||||||
}
|
}
|
||||||
return Err("game-chat 首版软预算已耗尽,拒绝继续请求 Provider".to_string());
|
return Err("game-chat 首版软预算已耗尽,拒绝继续请求 Provider".to_string());
|
||||||
};
|
};
|
||||||
|
let mut provider_observations = observations.to_vec();
|
||||||
|
if let Some(blocker) = game_chat_fast_path_external_repair_observation_at(root, runtime) {
|
||||||
|
provider_observations.push(blocker);
|
||||||
|
}
|
||||||
let provider_request = request_game_creator_agent_background_tool_plan_at(
|
let provider_request = request_game_creator_agent_background_tool_plan_at(
|
||||||
root,
|
root,
|
||||||
&runtime.agent_id,
|
&runtime.agent_id,
|
||||||
&runtime.session_id,
|
&runtime.session_id,
|
||||||
&runtime.run_id,
|
&runtime.run_id,
|
||||||
task,
|
task,
|
||||||
observations,
|
&provider_observations,
|
||||||
loop_index,
|
loop_index,
|
||||||
runtime.applied_steer_cursor,
|
runtime.applied_steer_cursor,
|
||||||
);
|
);
|
||||||
@@ -714,10 +716,10 @@ async fn run_game_creator_agent_background_task_pass_without_deadline(
|
|||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
let autonomous_manifest_can_wait = agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|
let autonomous_manifest_parent_can_wait = agent_id
|
||||||
|
== GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|
||||||
&& runtime.run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
&& runtime.run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||||
&& !game_chat_hard_budget_expired
|
&& !game_chat_hard_budget_expired
|
||||||
&& !autonomous_registered_derived_visuals_need_repair_at(&root)
|
|
||||||
&& !game_creator_agent_runtime_provider_action_batch_exists(
|
&& !game_creator_agent_runtime_provider_action_batch_exists(
|
||||||
&root,
|
&root,
|
||||||
&agent_id,
|
&agent_id,
|
||||||
@@ -732,32 +734,47 @@ async fn run_game_creator_agent_background_task_pass_without_deadline(
|
|||||||
&& isolated_join_completion_blocker_at(&root, &agent_id, &runtime.run_id).is_none()
|
&& isolated_join_completion_blocker_at(&root, &agent_id, &runtime.run_id).is_none()
|
||||||
&& static_delegate_completion_blocker_at(&root, &agent_id, &runtime.run_id)
|
&& static_delegate_completion_blocker_at(&root, &agent_id, &runtime.run_id)
|
||||||
.is_none();
|
.is_none();
|
||||||
if autonomous_manifest_can_wait {
|
if autonomous_manifest_parent_can_wait {
|
||||||
if let Err(error) = schedule_autonomous_game_build_ready_tasks_at(
|
// 已登记但损坏的派生视觉需要先由父 Run 规划修复,因此此时不再调度新的
|
||||||
&root,
|
// manifest child;但已经持久化并运行的 child 仍是当前 DAG 的活跃工作,
|
||||||
&agent_id,
|
// 父 Run 必须继续等待,不能提前落入 game-chat fixed-graph-stalled。
|
||||||
&runtime.run_id,
|
let scheduled_ready_tasks =
|
||||||
3,
|
if autonomous_registered_derived_visuals_need_repair_at(&root) {
|
||||||
) {
|
Vec::new()
|
||||||
return fail_game_creator_agent_background_context_at(
|
} else {
|
||||||
&root,
|
match schedule_autonomous_game_build_ready_tasks_at(
|
||||||
&agent_id,
|
|
||||||
&session_id,
|
|
||||||
runtime,
|
|
||||||
&format!("调度自主构建 manifest 任务失败:{error}"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let manifest_in_progress = match autonomous_manifest_dag_in_progress_at(&root) {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(error) => {
|
|
||||||
return fail_game_creator_agent_background_context_at(
|
|
||||||
&root,
|
&root,
|
||||||
&agent_id,
|
&agent_id,
|
||||||
&session_id,
|
&runtime.run_id,
|
||||||
runtime,
|
3,
|
||||||
&format!("读取自主构建 manifest 等待屏障失败:{error}"),
|
) {
|
||||||
);
|
Ok(tasks) => tasks,
|
||||||
|
Err(error) => {
|
||||||
|
return fail_game_creator_agent_background_context_at(
|
||||||
|
&root,
|
||||||
|
&agent_id,
|
||||||
|
&session_id,
|
||||||
|
runtime,
|
||||||
|
&format!("调度自主构建 manifest 任务失败:{error}"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let manifest_in_progress = if scheduled_ready_tasks.is_empty() {
|
||||||
|
match autonomous_manifest_dag_in_progress_at(&root) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => {
|
||||||
|
return fail_game_creator_agent_background_context_at(
|
||||||
|
&root,
|
||||||
|
&agent_id,
|
||||||
|
&session_id,
|
||||||
|
runtime,
|
||||||
|
&format!("读取自主构建 manifest 等待屏障失败:{error}"),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
};
|
};
|
||||||
if manifest_in_progress {
|
if manifest_in_progress {
|
||||||
let blocker =
|
let blocker =
|
||||||
@@ -1264,9 +1281,12 @@ async fn run_game_creator_agent_background_task_pass_without_deadline(
|
|||||||
&runtime.run_id,
|
&runtime.run_id,
|
||||||
);
|
);
|
||||||
let verified_delivery = match verification_gate {
|
let verified_delivery = match verification_gate {
|
||||||
Ok(gate) => agent_runtime_autonomous_verified_delivery_allows_plan_completion(
|
Ok(gate) => {
|
||||||
&agent_id, &gate,
|
agent_runtime_autonomous_verified_delivery_allows_plan_completion(
|
||||||
),
|
&agent_id, &gate,
|
||||||
|
) && autonomous_game_build_completion_blocker_at_locked(&root, &runtime)
|
||||||
|
.is_none()
|
||||||
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
return fail_game_creator_agent_background_context_at(
|
return fail_game_creator_agent_background_context_at(
|
||||||
&root,
|
&root,
|
||||||
@@ -2700,6 +2720,9 @@ async fn run_game_creator_agent_background_task_pass_without_deadline(
|
|||||||
observation_action_identity
|
observation_action_identity
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|identity| identity.0.as_str()),
|
.map(|identity| identity.0.as_str()),
|
||||||
|
observation_action_identity
|
||||||
|
.as_ref()
|
||||||
|
.map(|identity| identity.1.as_str()),
|
||||||
);
|
);
|
||||||
if observation.is_waiting_for_confirmation() {
|
if observation.is_waiting_for_confirmation() {
|
||||||
let mut pending_action = durable_action
|
let mut pending_action = durable_action
|
||||||
|
|||||||
+1217
-21
File diff suppressed because it is too large
Load Diff
@@ -606,6 +606,7 @@ async fn continue_game_creator_agent_pending_tool_action_within_stack_boundary(
|
|||||||
&action,
|
&action,
|
||||||
&observation,
|
&observation,
|
||||||
Some(&pending.action_id),
|
Some(&pending.action_id),
|
||||||
|
Some(&pending.action_fingerprint),
|
||||||
);
|
);
|
||||||
complete_agent_runtime_active_plan_step(
|
complete_agent_runtime_active_plan_step(
|
||||||
&mut runtime,
|
&mut runtime,
|
||||||
@@ -662,6 +663,7 @@ async fn continue_game_creator_agent_pending_tool_action_within_stack_boundary(
|
|||||||
&action,
|
&action,
|
||||||
&observation,
|
&observation,
|
||||||
Some(&pending.action_id),
|
Some(&pending.action_id),
|
||||||
|
Some(&pending.action_fingerprint),
|
||||||
);
|
);
|
||||||
complete_agent_runtime_active_plan_step(
|
complete_agent_runtime_active_plan_step(
|
||||||
&mut runtime,
|
&mut runtime,
|
||||||
@@ -1093,6 +1095,7 @@ pub(in crate::agent) fn mark_game_creator_agent_runtime_tool_observation_needs_r
|
|||||||
&pending.action,
|
&pending.action,
|
||||||
observation,
|
observation,
|
||||||
Some(&pending.action_id),
|
Some(&pending.action_id),
|
||||||
|
Some(&pending.action_fingerprint),
|
||||||
);
|
);
|
||||||
// needs-reconciliation 是外部结果未知边界,不是结构化计划步骤的确定失败。
|
// needs-reconciliation 是外部结果未知边界,不是结构化计划步骤的确定失败。
|
||||||
// 保持 active,后续同一 action 对账成功时才能完成该步骤,并让持久 context
|
// 保持 active,后续同一 action 对账成功时才能完成该步骤,并让持久 context
|
||||||
|
|||||||
+917
-52
File diff suppressed because it is too large
Load Diff
@@ -670,6 +670,19 @@ pub(in crate::agent) fn resume_game_creator_agent_background_tasks_unredacted_at
|
|||||||
resume_external_agent_runner(root)?;
|
resume_external_agent_runner(root)?;
|
||||||
return read_game_creator_agent_runtimes_at(root);
|
return read_game_creator_agent_runtimes_at(root);
|
||||||
}
|
}
|
||||||
|
if let Some(runtime_lock) = try_acquire_game_creator_agent_runtime_task_lock(
|
||||||
|
root,
|
||||||
|
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||||
|
)? {
|
||||||
|
if let Some(result) = repair_autonomous_manifest_parent_wake_reconciliation_projection_at(
|
||||||
|
root,
|
||||||
|
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||||
|
)? {
|
||||||
|
drop(runtime_lock);
|
||||||
|
return Ok(vec![result]);
|
||||||
|
}
|
||||||
|
drop(runtime_lock);
|
||||||
|
}
|
||||||
cleanup_orphaned_platform_art_generation_runtime_states_at(root)?;
|
cleanup_orphaned_platform_art_generation_runtime_states_at(root)?;
|
||||||
let agent_ids = collect_game_creator_agent_runtime_agent_ids(root)?;
|
let agent_ids = collect_game_creator_agent_runtime_agent_ids(root)?;
|
||||||
if !current_game_creator_agent_runtime_finalization_exists_at(root, &agent_ids)? {
|
if !current_game_creator_agent_runtime_finalization_exists_at(root, &agent_ids)? {
|
||||||
@@ -977,16 +990,26 @@ pub(in crate::agent) fn resume_game_creator_agent_background_tasks_unredacted_at
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if task.phase == "waiting-for-manifest-tasks" {
|
if task.phase == "waiting-for-manifest-tasks" {
|
||||||
if let Err(error) =
|
let scheduled_ready_tasks = match schedule_autonomous_game_build_ready_tasks_at(
|
||||||
schedule_autonomous_game_build_ready_tasks_at(root, &task.agent_id, &task.run_id, 3)
|
root,
|
||||||
{
|
&task.agent_id,
|
||||||
drop(runtime_lock);
|
&task.run_id,
|
||||||
mark_autonomous_manifest_parent_wake_needs_reconciliation_at(
|
3,
|
||||||
root,
|
) {
|
||||||
&agent_id,
|
Ok(tasks) => tasks,
|
||||||
&task.run_id,
|
Err(error) => {
|
||||||
&format!("Runner 重启恢复 manifest 任务图失败:{error}"),
|
drop(runtime_lock);
|
||||||
)?;
|
mark_autonomous_manifest_parent_wake_needs_reconciliation_at(
|
||||||
|
root,
|
||||||
|
&agent_id,
|
||||||
|
&task.run_id,
|
||||||
|
&format!("Runner 重启恢复 manifest 任务图失败:{error}"),
|
||||||
|
)?;
|
||||||
|
resumed.push(read_game_creator_agent_runtime_at(root, &agent_id)?);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if !scheduled_ready_tasks.is_empty() {
|
||||||
resumed.push(read_game_creator_agent_runtime_at(root, &agent_id)?);
|
resumed.push(read_game_creator_agent_runtime_at(root, &agent_id)?);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,6 +203,79 @@ pub(crate) fn spawn_next_game_creator_agent_background_task_drain_with_lock(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn spawn_started_game_creator_agent_background_task_drain_with_lock(
|
||||||
|
root: &Path,
|
||||||
|
agent_id: &str,
|
||||||
|
first_task: String,
|
||||||
|
first_state: AgentRuntimeState,
|
||||||
|
runtime_lock: AgentRuntimeTaskLock,
|
||||||
|
) -> Result<(), (String, AgentRuntimeTaskLock)> {
|
||||||
|
debug_assert!(!external_agent_runner_owns_background_execution());
|
||||||
|
#[cfg(test)]
|
||||||
|
let first_poll_delay = {
|
||||||
|
let path = root.join(format!(
|
||||||
|
".agent/runtime/test-delay-started-task-first-poll-{agent_id}"
|
||||||
|
));
|
||||||
|
let delay = fs::read_to_string(&path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|value| value.trim().parse::<u64>().ok())
|
||||||
|
.map(|milliseconds| Duration::from_millis(milliseconds.min(5_000)))
|
||||||
|
.unwrap_or_default();
|
||||||
|
let _ = fs::remove_file(path);
|
||||||
|
delay
|
||||||
|
};
|
||||||
|
let root = root.to_path_buf();
|
||||||
|
let agent_id = agent_id.to_string();
|
||||||
|
let (first_poll_sender, first_poll_receiver) = std::sync::mpsc::sync_channel(1);
|
||||||
|
let (runtime_lock_sender, runtime_lock_receiver) = std::sync::mpsc::sync_channel(1);
|
||||||
|
let worker_name = format!(
|
||||||
|
"agent-ready-task-{}",
|
||||||
|
sanitize_agent_runtime_text(&agent_id, 48)
|
||||||
|
);
|
||||||
|
if let Err(error) = std::thread::Builder::new()
|
||||||
|
.name(worker_name)
|
||||||
|
.stack_size(16 * 1024 * 1024)
|
||||||
|
.spawn(move || {
|
||||||
|
#[cfg(test)]
|
||||||
|
if !first_poll_delay.is_zero() {
|
||||||
|
std::thread::sleep(first_poll_delay);
|
||||||
|
}
|
||||||
|
tauri::async_runtime::block_on(async move {
|
||||||
|
if first_poll_sender.send(()).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Ok(runtime_lock) = runtime_lock_receiver.recv() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let _runtime_lock = runtime_lock;
|
||||||
|
drain_game_creator_agent_background_tasks(root, agent_id, first_task, first_state)
|
||||||
|
.await;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return Err((
|
||||||
|
format!("创建 Agent Runtime child execution worker 失败:{error}"),
|
||||||
|
runtime_lock,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if first_poll_receiver
|
||||||
|
.recv_timeout(Duration::from_secs(2))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return Err((
|
||||||
|
"Agent Runtime child execution future 未在 2 秒内开始轮询".to_string(),
|
||||||
|
runtime_lock,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Err(error) = runtime_lock_sender.send(runtime_lock) {
|
||||||
|
return Err((
|
||||||
|
"Agent Runtime child execution future 在接管执行锁前已退出".to_string(),
|
||||||
|
error.0,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(in crate::agent) fn fail_game_creator_agent_background_context_at(
|
pub(in crate::agent) fn fail_game_creator_agent_background_context_at(
|
||||||
root: &Path,
|
root: &Path,
|
||||||
agent_id: &str,
|
agent_id: &str,
|
||||||
|
|||||||
@@ -616,7 +616,7 @@ fn validate_autonomous_game_build_ready_task_parent_at(
|
|||||||
Ok(binding)
|
Ok(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn autonomous_game_build_root_task_is_active(task: &AgentRuntimeTaskRecord) -> bool {
|
pub(crate) fn autonomous_game_build_root_task_is_active(task: &AgentRuntimeTaskRecord) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
task.status.as_str(),
|
task.status.as_str(),
|
||||||
"pending" | "running" | "waiting-for-confirmation" | "waiting-for-user-input"
|
"pending" | "running" | "waiting-for-confirmation" | "waiting-for-user-input"
|
||||||
@@ -626,7 +626,7 @@ fn autonomous_game_build_root_task_is_active(task: &AgentRuntimeTaskRecord) -> b
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_autonomous_game_build_root_task_at(
|
pub(crate) fn current_autonomous_game_build_root_task_at(
|
||||||
root: &Path,
|
root: &Path,
|
||||||
) -> Result<Option<AgentRuntimeTaskRecord>, String> {
|
) -> Result<Option<AgentRuntimeTaskRecord>, String> {
|
||||||
let path = game_creator_agent_runtime_task_path(root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID);
|
let path = game_creator_agent_runtime_task_path(root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID);
|
||||||
@@ -1042,8 +1042,11 @@ pub(crate) fn schedule_autonomous_game_build_ready_tasks_at(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
for (task, result, record, needs_notification, runtime_lock) in scheduled {
|
for (task, mut result, record, needs_notification, runtime_lock) in scheduled {
|
||||||
append_agent_db_record(
|
// This record is diagnostic only. The durable child task and manifest
|
||||||
|
// transition already exist, so an audit sink failure must not strand
|
||||||
|
// the child in queued before its execution worker is started.
|
||||||
|
let _ = append_agent_db_record(
|
||||||
root,
|
root,
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"recordType": "agent.runtime.autonomous_ready_task.scheduled",
|
"recordType": "agent.runtime.autonomous_ready_task.scheduled",
|
||||||
@@ -1059,14 +1062,117 @@ pub(crate) fn schedule_autonomous_game_build_ready_tasks_at(
|
|||||||
"role": task.role.clone(),
|
"role": task.role.clone(),
|
||||||
"recovered": needs_notification,
|
"recovered": needs_notification,
|
||||||
}),
|
}),
|
||||||
)?;
|
);
|
||||||
if game_creator_agent_runtime_terminal_status(&record).is_none() {
|
if game_creator_agent_runtime_terminal_status(&record).is_none() {
|
||||||
if let Some(runtime_lock) = runtime_lock {
|
if let Some(runtime_lock) = runtime_lock {
|
||||||
spawn_next_game_creator_agent_background_task_drain_with_lock(
|
if external_agent_runner_owns_background_execution() {
|
||||||
root,
|
spawn_next_game_creator_agent_background_task_drain_with_lock(
|
||||||
&record.agent_id,
|
root,
|
||||||
runtime_lock,
|
&record.agent_id,
|
||||||
);
|
runtime_lock,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
match (|| {
|
||||||
|
#[cfg(test)]
|
||||||
|
{
|
||||||
|
let injected_failure = root.join(format!(
|
||||||
|
".agent/runtime/test-fail-autonomous-ready-task-start-{}",
|
||||||
|
record.agent_id
|
||||||
|
));
|
||||||
|
if fs::remove_file(injected_failure).is_ok() {
|
||||||
|
return Err(
|
||||||
|
"测试注入 autonomous ready-task child 启动失败".to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start_game_creator_agent_runtime_task_for_session_at(
|
||||||
|
root,
|
||||||
|
&record.agent_id,
|
||||||
|
Some(&record.session_id),
|
||||||
|
&record.task,
|
||||||
|
&record.run_id,
|
||||||
|
&record.source,
|
||||||
|
"后台任务从队列开始执行",
|
||||||
|
game_creator_agent_background_task_default_plan(),
|
||||||
|
)
|
||||||
|
})() {
|
||||||
|
Ok(state) => {
|
||||||
|
let _ = append_game_creator_agent_background_task_started_record(
|
||||||
|
root, &state,
|
||||||
|
);
|
||||||
|
let launch =
|
||||||
|
spawn_started_game_creator_agent_background_task_drain_with_lock(
|
||||||
|
root,
|
||||||
|
&record.agent_id,
|
||||||
|
record.task.clone(),
|
||||||
|
state.clone(),
|
||||||
|
runtime_lock,
|
||||||
|
);
|
||||||
|
match launch {
|
||||||
|
Ok(()) => {
|
||||||
|
result.state = state.clone();
|
||||||
|
result.task_queue = state.task_queue.clone();
|
||||||
|
}
|
||||||
|
Err((error, runtime_lock)) => {
|
||||||
|
let error = format!(
|
||||||
|
"autonomous ready-task child execution 启动失败:taskId={};{error}",
|
||||||
|
task.id
|
||||||
|
);
|
||||||
|
let failure = fail_game_creator_agent_runtime_turn_at(
|
||||||
|
root, state, &error,
|
||||||
|
);
|
||||||
|
let error = match failure.and_then(|failed| {
|
||||||
|
project_autonomous_manifest_ready_task_terminal_at(
|
||||||
|
root, &failed,
|
||||||
|
)?;
|
||||||
|
Ok(failed)
|
||||||
|
}) {
|
||||||
|
Ok(failed) => {
|
||||||
|
result.task_queue = failed.task_queue.clone();
|
||||||
|
result.state = failed;
|
||||||
|
error
|
||||||
|
}
|
||||||
|
Err(persistence_error) => format!(
|
||||||
|
"{error};失败状态或任务图收口失败:{persistence_error}"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
drop(runtime_lock);
|
||||||
|
if first_error.is_none() {
|
||||||
|
first_error = Some(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
let error = format!(
|
||||||
|
"autonomous ready-task child 启动失败:taskId={};{error}",
|
||||||
|
task.id
|
||||||
|
);
|
||||||
|
let fallback = agent_runtime_state_from_task_record(&record);
|
||||||
|
let failure =
|
||||||
|
fail_game_creator_agent_runtime_turn_at(root, fallback, &error);
|
||||||
|
let error = match failure.and_then(|failed| {
|
||||||
|
project_autonomous_manifest_ready_task_terminal_at(root, &failed)?;
|
||||||
|
Ok(failed)
|
||||||
|
}) {
|
||||||
|
Ok(failed) => {
|
||||||
|
result.task_queue = failed.task_queue.clone();
|
||||||
|
result.state = failed;
|
||||||
|
error
|
||||||
|
}
|
||||||
|
Err(persistence_error) => {
|
||||||
|
format!(
|
||||||
|
"{error};失败状态或任务图收口失败:{persistence_error}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
drop(runtime_lock);
|
||||||
|
if first_error.is_none() {
|
||||||
|
first_error = Some(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if needs_notification
|
if needs_notification
|
||||||
|
|||||||
+2183
-69
File diff suppressed because it is too large
Load Diff
+894
-14
File diff suppressed because it is too large
Load Diff
@@ -251,6 +251,11 @@ pub(super) fn start_game_creator_agent_runtime_task_for_session_in_session_lane_
|
|||||||
.ok()
|
.ok()
|
||||||
.map(|result| result.state);
|
.map(|result| result.state);
|
||||||
let mut state = default_game_creator_agent_runtime_state(&agent_id, &run_id);
|
let mut state = default_game_creator_agent_runtime_state(&agent_id, &run_id);
|
||||||
|
state.started_at = queued_task_record
|
||||||
|
.as_ref()
|
||||||
|
.map(|record| record.updated_at)
|
||||||
|
.filter(|updated_at| *updated_at > 0)
|
||||||
|
.unwrap_or_else(unix_timestamp);
|
||||||
state.session_id = session_id;
|
state.session_id = session_id;
|
||||||
state.source = source.trim().to_string();
|
state.source = source.trim().to_string();
|
||||||
let (run_profile, run_profile_binding_fingerprint) = agent_runtime_run_profile_identity_at(
|
let (run_profile, run_profile_binding_fingerprint) = agent_runtime_run_profile_identity_at(
|
||||||
@@ -286,6 +291,9 @@ pub(super) fn start_game_creator_agent_runtime_task_for_session_in_session_lane_
|
|||||||
&& previous_state.source == state.source
|
&& previous_state.source == state.source
|
||||||
&& previous_state.current_task == state.current_task;
|
&& previous_state.current_task == state.current_task;
|
||||||
if same_runtime_run {
|
if same_runtime_run {
|
||||||
|
if previous_state.started_at > 0 {
|
||||||
|
state.started_at = previous_state.started_at;
|
||||||
|
}
|
||||||
state.loop_iteration = previous_state.loop_iteration;
|
state.loop_iteration = previous_state.loop_iteration;
|
||||||
state.max_loop_iterations = previous_state.max_loop_iterations;
|
state.max_loop_iterations = previous_state.max_loop_iterations;
|
||||||
state.tool_action_budget = previous_state.tool_action_budget;
|
state.tool_action_budget = previous_state.tool_action_budget;
|
||||||
@@ -1453,6 +1461,7 @@ pub(crate) fn default_game_creator_agent_runtime_state(
|
|||||||
context_usage: AgentRuntimeContextUsage::default(),
|
context_usage: AgentRuntimeContextUsage::default(),
|
||||||
last_response: None,
|
last_response: None,
|
||||||
error: None,
|
error: None,
|
||||||
|
started_at: 0,
|
||||||
updated_at: unix_timestamp(),
|
updated_at: unix_timestamp(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2823,6 +2832,17 @@ pub(super) fn append_unique_game_creator_agent_runtime_pending_task(
|
|||||||
requested_run_profile: Option<&str>,
|
requested_run_profile: Option<&str>,
|
||||||
task_link: Option<&AgentRuntimeTaskLink>,
|
task_link: Option<&AgentRuntimeTaskLink>,
|
||||||
) -> Result<AgentRuntimeTaskRecord, String> {
|
) -> Result<AgentRuntimeTaskRecord, String> {
|
||||||
|
let autonomous_root_project_lock = (agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|
||||||
|
&& task_link.is_none()
|
||||||
|
&& requested_run_profile
|
||||||
|
.is_some_and(|profile| profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD))
|
||||||
|
.then(|| {
|
||||||
|
acquire_game_creator_agent_runtime_project_write_lock_with_wait(
|
||||||
|
root,
|
||||||
|
"runtime.autonomous-root.create",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
let _journal_lock = acquire_game_creator_agent_runtime_task_journal_lock(root, agent_id)?;
|
let _journal_lock = acquire_game_creator_agent_runtime_task_journal_lock(root, agent_id)?;
|
||||||
let run_id = unique_game_creator_agent_runtime_run_id(root, agent_id, requested_run_id)?;
|
let run_id = unique_game_creator_agent_runtime_run_id(root, agent_id, requested_run_id)?;
|
||||||
let run_profile_binding = bind_game_creator_agent_runtime_run_profile_at(
|
let run_profile_binding = bind_game_creator_agent_runtime_run_profile_at(
|
||||||
@@ -2865,6 +2885,8 @@ pub(super) fn append_unique_game_creator_agent_runtime_pending_task(
|
|||||||
updated_at: unix_timestamp(),
|
updated_at: unix_timestamp(),
|
||||||
};
|
};
|
||||||
append_game_creator_agent_runtime_task_record_unlocked(root, &record)?;
|
append_game_creator_agent_runtime_task_record_unlocked(root, &record)?;
|
||||||
|
drop(_journal_lock);
|
||||||
|
drop(autonomous_root_project_lock);
|
||||||
if let Err(error) = ensure_autonomous_completion_contract_for_task_at(root, &record) {
|
if let Err(error) = ensure_autonomous_completion_contract_for_task_at(root, &record) {
|
||||||
let public_error = redact_agent_runtime_project_paths(root, &error, 500);
|
let public_error = redact_agent_runtime_project_paths(root, &error, 500);
|
||||||
let failed = AgentRuntimeTaskRecord {
|
let failed = AgentRuntimeTaskRecord {
|
||||||
@@ -2882,7 +2904,7 @@ pub(super) fn append_unique_game_creator_agent_runtime_pending_task(
|
|||||||
Ok(record)
|
Ok(record)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn append_or_read_exact_game_creator_agent_runtime_pending_task(
|
pub(crate) fn append_or_read_exact_game_creator_agent_runtime_pending_task(
|
||||||
root: &Path,
|
root: &Path,
|
||||||
agent_id: &str,
|
agent_id: &str,
|
||||||
session_id: &str,
|
session_id: &str,
|
||||||
@@ -3111,19 +3133,32 @@ pub(super) fn read_recent_game_creator_agent_runtime_events_for_session(
|
|||||||
pub(super) struct AgentRuntimeTaskSnapshot {
|
pub(super) struct AgentRuntimeTaskSnapshot {
|
||||||
pub(super) task_queue: AgentRuntimeTaskQueueSummary,
|
pub(super) task_queue: AgentRuntimeTaskQueueSummary,
|
||||||
pub(super) recent_tasks: Vec<AgentRuntimeTaskRecord>,
|
pub(super) recent_tasks: Vec<AgentRuntimeTaskRecord>,
|
||||||
|
pub(super) run_started_at: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn read_game_creator_agent_runtime_task_snapshot(
|
pub(super) fn read_game_creator_agent_runtime_task_snapshot(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
) -> Result<AgentRuntimeTaskSnapshot, String> {
|
) -> Result<AgentRuntimeTaskSnapshot, String> {
|
||||||
read_game_creator_agent_runtime_task_snapshot_for_session(path, None)
|
read_game_creator_agent_runtime_task_snapshot_for_session(path, None, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn read_game_creator_agent_runtime_task_snapshot_for_session(
|
pub(super) fn read_game_creator_agent_runtime_task_snapshot_for_session(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
session_id: Option<&str>,
|
session_id: Option<&str>,
|
||||||
|
run_id: Option<&str>,
|
||||||
) -> Result<AgentRuntimeTaskSnapshot, String> {
|
) -> Result<AgentRuntimeTaskSnapshot, String> {
|
||||||
let records = read_all_game_creator_agent_runtime_tasks(path)?;
|
let records = read_all_game_creator_agent_runtime_tasks(path)?;
|
||||||
|
let run_started_at = run_id.and_then(|run_id| {
|
||||||
|
records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.run_id == run_id
|
||||||
|
&& session_id.map_or(true, |session_id| record.session_id == session_id)
|
||||||
|
})
|
||||||
|
.map(|record| record.updated_at)
|
||||||
|
.filter(|updated_at| *updated_at > 0)
|
||||||
|
.min()
|
||||||
|
});
|
||||||
let latest = latest_game_creator_agent_runtime_tasks(records)
|
let latest = latest_game_creator_agent_runtime_tasks(records)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|record| session_id.map_or(true, |session_id| record.session_id == session_id))
|
.filter(|record| session_id.map_or(true, |session_id| record.session_id == session_id))
|
||||||
@@ -3136,6 +3171,7 @@ pub(super) fn read_game_creator_agent_runtime_task_snapshot_for_session(
|
|||||||
Ok(AgentRuntimeTaskSnapshot {
|
Ok(AgentRuntimeTaskSnapshot {
|
||||||
task_queue,
|
task_queue,
|
||||||
recent_tasks: recent,
|
recent_tasks: recent,
|
||||||
|
run_started_at,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ pub(in crate::agent) fn observe_agent_runtime_memory(
|
|||||||
pub(in crate::agent) fn observe_agent_runtime_memory_write(
|
pub(in crate::agent) fn observe_agent_runtime_memory_write(
|
||||||
root: &Path,
|
root: &Path,
|
||||||
agent_id: &str,
|
agent_id: &str,
|
||||||
|
run_id: &str,
|
||||||
input: &serde_json::Value,
|
input: &serde_json::Value,
|
||||||
) -> AgentRuntimeToolObservation {
|
) -> AgentRuntimeToolObservation {
|
||||||
let scope = input
|
let scope = input
|
||||||
@@ -105,6 +106,16 @@ pub(in crate::agent) fn observe_agent_runtime_memory_write(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if let Err(error) =
|
||||||
|
ensure_current_autonomous_ready_child_mutation_at_locked(root, agent_id, run_id)
|
||||||
|
{
|
||||||
|
return AgentRuntimeToolObservation {
|
||||||
|
tool: "memory.write".to_string(),
|
||||||
|
status: "failed".to_string(),
|
||||||
|
summary: sanitize_agent_runtime_text(&error, 240),
|
||||||
|
detail: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
if let Err(error) = advance_agent_runtime_project_revision_locked(root) {
|
if let Err(error) = advance_agent_runtime_project_revision_locked(root) {
|
||||||
return agent_runtime_revision_advance_failure_observation(root, "memory.write", &error);
|
return agent_runtime_revision_advance_failure_observation(root, "memory.write", &error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use super::*;
|
|||||||
pub(in crate::agent) fn observe_agent_runtime_blackboard_write(
|
pub(in crate::agent) fn observe_agent_runtime_blackboard_write(
|
||||||
root: &Path,
|
root: &Path,
|
||||||
agent_id: &str,
|
agent_id: &str,
|
||||||
|
run_id: &str,
|
||||||
input: &serde_json::Value,
|
input: &serde_json::Value,
|
||||||
) -> AgentRuntimeToolObservation {
|
) -> AgentRuntimeToolObservation {
|
||||||
let content = agent_runtime_tool_input_text(input, &["content", "summary", "message"]);
|
let content = agent_runtime_tool_input_text(input, &["content", "summary", "message"]);
|
||||||
@@ -25,6 +26,16 @@ pub(in crate::agent) fn observe_agent_runtime_blackboard_write(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if let Err(error) =
|
||||||
|
ensure_current_autonomous_ready_child_mutation_at_locked(root, agent_id, run_id)
|
||||||
|
{
|
||||||
|
return AgentRuntimeToolObservation {
|
||||||
|
tool: "blackboard.write".to_string(),
|
||||||
|
status: "failed".to_string(),
|
||||||
|
summary: sanitize_agent_runtime_text(&error, 240),
|
||||||
|
detail: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
if let Err(error) = advance_agent_runtime_project_revision_locked(root) {
|
if let Err(error) = advance_agent_runtime_project_revision_locked(root) {
|
||||||
return agent_runtime_revision_advance_failure_observation(
|
return agent_runtime_revision_advance_failure_observation(
|
||||||
root,
|
root,
|
||||||
@@ -112,6 +123,16 @@ pub(crate) fn observe_agent_runtime_agent_message(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if let Err(error) =
|
||||||
|
ensure_current_autonomous_ready_child_mutation_at_locked(root, agent_id, run_id)
|
||||||
|
{
|
||||||
|
return AgentRuntimeToolObservation {
|
||||||
|
tool: "agent.message".to_string(),
|
||||||
|
status: "failed".to_string(),
|
||||||
|
summary: sanitize_agent_runtime_text(&error, 240),
|
||||||
|
detail: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
let content = truncate_agent_runtime_text(sanitize_prompt_context(&content).as_str(), 1_200);
|
let content = truncate_agent_runtime_text(sanitize_prompt_context(&content).as_str(), 1_200);
|
||||||
let message = format!("来自 {agent_id} 的定向消息:{content}");
|
let message = format!("来自 {agent_id} 的定向消息:{content}");
|
||||||
let result = resolve_agent_conversation_session_id_at(root, &target_agent_id, None, true)
|
let result = resolve_agent_conversation_session_id_at(root, &target_agent_id, None, true)
|
||||||
@@ -397,6 +418,16 @@ pub(crate) fn observe_agent_runtime_agent_delegate(
|
|||||||
detail: None,
|
detail: None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if let Err(error) =
|
||||||
|
ensure_current_autonomous_ready_child_mutation_at_locked(root, agent_id, parent_run_id)
|
||||||
|
{
|
||||||
|
return AgentRuntimeToolObservation {
|
||||||
|
tool: "agent.delegate".to_string(),
|
||||||
|
status: "failed".to_string(),
|
||||||
|
summary: sanitize_agent_runtime_text(&error, 240),
|
||||||
|
detail: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
let action_identity = action_id
|
let action_identity = action_id
|
||||||
.filter(|value| !value.trim().is_empty())
|
.filter(|value| !value.trim().is_empty())
|
||||||
.map(str::to_string)
|
.map(str::to_string)
|
||||||
|
|||||||
@@ -359,13 +359,13 @@ pub(in crate::agent) fn wake_waiting_autonomous_manifest_parent_run_at(
|
|||||||
return Err("manifest parent-wake 只允许自主构建根 Supervisor".to_string());
|
return Err("manifest parent-wake 只允许自主构建根 Supervisor".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
schedule_autonomous_game_build_ready_tasks_at(
|
let scheduled_ready_tasks = schedule_autonomous_game_build_ready_tasks_at(
|
||||||
root,
|
root,
|
||||||
¤t_task.agent_id,
|
¤t_task.agent_id,
|
||||||
¤t_task.run_id,
|
¤t_task.run_id,
|
||||||
3,
|
3,
|
||||||
)?;
|
)?;
|
||||||
if autonomous_manifest_dag_in_progress_at(root)? {
|
if !scheduled_ready_tasks.is_empty() || autonomous_manifest_dag_in_progress_at(root)? {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user