修复游戏创作编排恢复与进度可观测性
提取并固化 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)]
|
||||
pub(crate) use project_gates::{
|
||||
ensure_current_autonomous_ready_child_mutation_at_locked,
|
||||
supervisor_collaboration_policy_completion_blocker_for_test_at,
|
||||
supervisor_orchestrator_mutation_block_after_dispatch_for_test,
|
||||
};
|
||||
|
||||
@@ -50,12 +50,17 @@ pub(crate) fn append_agent_runtime_tool_call_record(
|
||||
action: &AgentRuntimeToolAction,
|
||||
observation: &AgentRuntimeToolObservation,
|
||||
action_id: Option<&str>,
|
||||
action_fingerprint: Option<&str>,
|
||||
) {
|
||||
let record = AgentRuntimeToolCallRecord {
|
||||
action_id: action_id.map(ToString::to_string),
|
||||
tool: observation.tool.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),
|
||||
reason: action
|
||||
.reason
|
||||
|
||||
@@ -132,7 +132,7 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_
|
||||
false,
|
||||
|| 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(
|
||||
root,
|
||||
agent_id,
|
||||
@@ -285,8 +285,8 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_
|
||||
false,
|
||||
|| observe_agent_runtime_task_list(root, agent_id, run_id),
|
||||
),
|
||||
"task.create" => observe_agent_runtime_task_create(root, agent_id, &action.input),
|
||||
"task.update" => observe_agent_runtime_task_update(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, run_id, &action.input),
|
||||
"command.exec" => {
|
||||
observe_agent_runtime_command_exec(
|
||||
root,
|
||||
@@ -386,7 +386,9 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_
|
||||
)
|
||||
.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" => {
|
||||
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,
|
||||
&waiting_observation,
|
||||
Some(&pending.action_id),
|
||||
Some(&pending.action_fingerprint),
|
||||
);
|
||||
runtime.pending_tool_action = Some(pending.summary());
|
||||
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,
|
||||
) -> Result<bool, String> {
|
||||
let manifest = read_manifest_for_project(root)?;
|
||||
let source = read_game_creator_agent_runtime_at(root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID)
|
||||
.ok()
|
||||
.filter(|runtime| {
|
||||
runtime.state.run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||
&& agent_runtime_supervisor_source_is_trusted(&runtime.state.source)
|
||||
})
|
||||
.map(|runtime| runtime.state.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)
|
||||
let current_root = current_autonomous_game_build_root_task_at(root)?.ok_or_else(|| {
|
||||
"无法解析当前自主构建根 Run 的可信 source,拒绝按 GUI 完整 DAG 回退".to_string()
|
||||
})?;
|
||||
if !autonomous_game_build_root_task_is_active(¤t_root) {
|
||||
return Ok(false);
|
||||
}
|
||||
let seed_task_ids = autonomous_manifest_seed_tasks_for_source(¤t_root.source)
|
||||
.into_iter()
|
||||
.map(|task| task.id)
|
||||
.collect::<BTreeSet<_>>();
|
||||
@@ -572,19 +551,58 @@ pub(in crate::agent) fn autonomous_manifest_dag_in_progress_at(
|
||||
.iter()
|
||||
.filter(|task| seed_task_ids.contains(&task.id))
|
||||
.collect::<Vec<_>>();
|
||||
let started = seed_tasks
|
||||
.iter()
|
||||
.any(|task| task.status != GameCreationAppTaskStatus::Pending);
|
||||
let running = seed_tasks
|
||||
.iter()
|
||||
.any(|task| task.status == GameCreationAppTaskStatus::Running);
|
||||
let completed = seed_tasks
|
||||
.iter()
|
||||
.all(|task| task.status == GameCreationAppTaskStatus::Completed);
|
||||
let failed = seed_tasks
|
||||
.iter()
|
||||
.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(
|
||||
|
||||
@@ -776,6 +776,7 @@ pub(in crate::agent) fn project_game_creator_agent_runtime_parallel_read_batch(
|
||||
&pending.action,
|
||||
&observation,
|
||||
Some(&pending.action_id),
|
||||
Some(&pending.action_fingerprint),
|
||||
);
|
||||
runtime.pending_tool_action = None;
|
||||
runtime.status = "running".to_string();
|
||||
|
||||
@@ -142,6 +142,7 @@ pub(crate) fn prepare_agent_runtime_project_mutation_locked(
|
||||
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 gate = read_game_creator_agent_runtime_verification_gate(root, agent_id, run_id)?;
|
||||
let next_revision = revision
|
||||
@@ -176,6 +177,159 @@ pub(crate) fn prepare_agent_runtime_project_mutation_locked(
|
||||
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(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
|
||||
+2
@@ -689,6 +689,7 @@ pub(in crate::agent) fn persist_game_creator_agent_runtime_provider_batch_waitin
|
||||
&pending.action,
|
||||
observation,
|
||||
Some(&pending.action_id),
|
||||
Some(&pending.action_fingerprint),
|
||||
);
|
||||
complete_agent_runtime_active_plan_step(
|
||||
runtime,
|
||||
@@ -788,6 +789,7 @@ pub(in crate::agent) fn project_game_creator_agent_runtime_provider_batch_abort(
|
||||
&pending.action,
|
||||
&observation,
|
||||
Some(&pending.action_id),
|
||||
Some(&pending.action_fingerprint),
|
||||
);
|
||||
complete_agent_runtime_active_plan_step(runtime, "failed", &observation_summary);
|
||||
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_CLI_SOURCE: &str = "project-supervisor-cli";
|
||||
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 {
|
||||
matches!(
|
||||
@@ -277,8 +279,12 @@ pub(crate) use provider_recovery::{
|
||||
};
|
||||
#[cfg(test)]
|
||||
pub(crate) use provider_recovery::{
|
||||
drive_waiting_autonomous_manifest_parent_wake_budget_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,
|
||||
repair_autonomous_manifest_parent_wake_reconciliation_projection_for_test,
|
||||
};
|
||||
pub(crate) use recovery_scan::{
|
||||
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,
|
||||
spawn_next_game_creator_agent_background_task_drain,
|
||||
spawn_next_game_creator_agent_background_task_drain_with_lock,
|
||||
spawn_started_game_creator_agent_background_task_drain_with_lock,
|
||||
};
|
||||
#[cfg(test)]
|
||||
pub(crate) use task_start::start_game_creator_agent_background_task_with_session_lane_hook_at;
|
||||
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,
|
||||
project_autonomous_manifest_ready_task_terminal_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 =
|
||||
read_recent_game_creator_agent_runtime_events_for_session(&event_path, session_id)?;
|
||||
let task_snapshot =
|
||||
read_game_creator_agent_runtime_task_snapshot_for_session(&task_path, session_id)?;
|
||||
let task_snapshot = read_game_creator_agent_runtime_task_snapshot_for_session(
|
||||
&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();
|
||||
let response_stream =
|
||||
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";
|
||||
|
||||
const FALLBACK_THEME_MARKER: &str = "__GAME_CHAT_THEME__";
|
||||
pub(super) const GAME_CHAT_CODE_COMPLETION_REPAIR_STEP: &str = "修复 Runtime 完成门诊断并重新验证";
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
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(
|
||||
root: &Path,
|
||||
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(
|
||||
root: &Path,
|
||||
runtime: &AgentRuntimeState,
|
||||
@@ -898,13 +1186,29 @@ pub(crate) fn game_chat_fast_path_plan_at(
|
||||
}
|
||||
}
|
||||
"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 gate = read_game_creator_agent_runtime_verification_gate(
|
||||
root,
|
||||
&runtime.agent_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
|
||||
&& gate.verified_revision == Some(revision.revision)
|
||||
&& 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);
|
||||
|
||||
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(
|
||||
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]
|
||||
fn art_slice_completion_validation_rejects_tampering_and_duplicate_pixels() {
|
||||
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,
|
||||
&observation,
|
||||
Some(&next_pending.action_id),
|
||||
Some(&next_pending.action_fingerprint),
|
||||
);
|
||||
activate_agent_runtime_plan_step(
|
||||
runtime,
|
||||
|
||||
@@ -112,9 +112,7 @@ async fn request_game_creator_agent_tool_plan_with_game_chat_budget_at(
|
||||
.map(RequestedAgentRuntimeToolPlanOutcome::Ready);
|
||||
}
|
||||
if runtime.agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
|
||||
return Err(
|
||||
"game-chat 首版固定任务图无法继续推进,拒绝回退到普通 Provider 协作波".to_string(),
|
||||
);
|
||||
return Err(GAME_CHAT_FIXED_TASK_GRAPH_STALLED_ERROR.to_string());
|
||||
}
|
||||
let Some(timeout) = game_chat_fast_path_provider_timeout(&budget) else {
|
||||
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());
|
||||
};
|
||||
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(
|
||||
root,
|
||||
&runtime.agent_id,
|
||||
&runtime.session_id,
|
||||
&runtime.run_id,
|
||||
task,
|
||||
observations,
|
||||
&provider_observations,
|
||||
loop_index,
|
||||
runtime.applied_steer_cursor,
|
||||
);
|
||||
@@ -714,10 +716,10 @@ async fn run_game_creator_agent_background_task_pass_without_deadline(
|
||||
} else {
|
||||
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
|
||||
&& !game_chat_hard_budget_expired
|
||||
&& !autonomous_registered_derived_visuals_need_repair_at(&root)
|
||||
&& !game_creator_agent_runtime_provider_action_batch_exists(
|
||||
&root,
|
||||
&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()
|
||||
&& static_delegate_completion_blocker_at(&root, &agent_id, &runtime.run_id)
|
||||
.is_none();
|
||||
if autonomous_manifest_can_wait {
|
||||
if let Err(error) = schedule_autonomous_game_build_ready_tasks_at(
|
||||
&root,
|
||||
&agent_id,
|
||||
&runtime.run_id,
|
||||
3,
|
||||
) {
|
||||
return fail_game_creator_agent_background_context_at(
|
||||
&root,
|
||||
&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(
|
||||
if autonomous_manifest_parent_can_wait {
|
||||
// 已登记但损坏的派生视觉需要先由父 Run 规划修复,因此此时不再调度新的
|
||||
// manifest child;但已经持久化并运行的 child 仍是当前 DAG 的活跃工作,
|
||||
// 父 Run 必须继续等待,不能提前落入 game-chat fixed-graph-stalled。
|
||||
let scheduled_ready_tasks =
|
||||
if autonomous_registered_derived_visuals_need_repair_at(&root) {
|
||||
Vec::new()
|
||||
} else {
|
||||
match schedule_autonomous_game_build_ready_tasks_at(
|
||||
&root,
|
||||
&agent_id,
|
||||
&session_id,
|
||||
runtime,
|
||||
&format!("读取自主构建 manifest 等待屏障失败:{error}"),
|
||||
);
|
||||
&runtime.run_id,
|
||||
3,
|
||||
) {
|
||||
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 {
|
||||
let blocker =
|
||||
@@ -1264,9 +1281,12 @@ async fn run_game_creator_agent_background_task_pass_without_deadline(
|
||||
&runtime.run_id,
|
||||
);
|
||||
let verified_delivery = match verification_gate {
|
||||
Ok(gate) => agent_runtime_autonomous_verified_delivery_allows_plan_completion(
|
||||
&agent_id, &gate,
|
||||
),
|
||||
Ok(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) => {
|
||||
return fail_game_creator_agent_background_context_at(
|
||||
&root,
|
||||
@@ -2700,6 +2720,9 @@ async fn run_game_creator_agent_background_task_pass_without_deadline(
|
||||
observation_action_identity
|
||||
.as_ref()
|
||||
.map(|identity| identity.0.as_str()),
|
||||
observation_action_identity
|
||||
.as_ref()
|
||||
.map(|identity| identity.1.as_str()),
|
||||
);
|
||||
if observation.is_waiting_for_confirmation() {
|
||||
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,
|
||||
&observation,
|
||||
Some(&pending.action_id),
|
||||
Some(&pending.action_fingerprint),
|
||||
);
|
||||
complete_agent_runtime_active_plan_step(
|
||||
&mut runtime,
|
||||
@@ -662,6 +663,7 @@ async fn continue_game_creator_agent_pending_tool_action_within_stack_boundary(
|
||||
&action,
|
||||
&observation,
|
||||
Some(&pending.action_id),
|
||||
Some(&pending.action_fingerprint),
|
||||
);
|
||||
complete_agent_runtime_active_plan_step(
|
||||
&mut runtime,
|
||||
@@ -1093,6 +1095,7 @@ pub(in crate::agent) fn mark_game_creator_agent_runtime_tool_observation_needs_r
|
||||
&pending.action,
|
||||
observation,
|
||||
Some(&pending.action_id),
|
||||
Some(&pending.action_fingerprint),
|
||||
);
|
||||
// needs-reconciliation 是外部结果未知边界,不是结构化计划步骤的确定失败。
|
||||
// 保持 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)?;
|
||||
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)?;
|
||||
let agent_ids = collect_game_creator_agent_runtime_agent_ids(root)?;
|
||||
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 let Err(error) =
|
||||
schedule_autonomous_game_build_ready_tasks_at(root, &task.agent_id, &task.run_id, 3)
|
||||
{
|
||||
drop(runtime_lock);
|
||||
mark_autonomous_manifest_parent_wake_needs_reconciliation_at(
|
||||
root,
|
||||
&agent_id,
|
||||
&task.run_id,
|
||||
&format!("Runner 重启恢复 manifest 任务图失败:{error}"),
|
||||
)?;
|
||||
let scheduled_ready_tasks = match schedule_autonomous_game_build_ready_tasks_at(
|
||||
root,
|
||||
&task.agent_id,
|
||||
&task.run_id,
|
||||
3,
|
||||
) {
|
||||
Ok(tasks) => tasks,
|
||||
Err(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)?);
|
||||
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(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
|
||||
@@ -616,7 +616,7 @@ fn validate_autonomous_game_build_ready_task_parent_at(
|
||||
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!(
|
||||
task.status.as_str(),
|
||||
"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,
|
||||
) -> Result<Option<AgentRuntimeTaskRecord>, String> {
|
||||
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();
|
||||
for (task, result, record, needs_notification, runtime_lock) in scheduled {
|
||||
append_agent_db_record(
|
||||
for (task, mut result, record, needs_notification, runtime_lock) in scheduled {
|
||||
// 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,
|
||||
serde_json::json!({
|
||||
"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(),
|
||||
"recovered": needs_notification,
|
||||
}),
|
||||
)?;
|
||||
);
|
||||
if game_creator_agent_runtime_terminal_status(&record).is_none() {
|
||||
if let Some(runtime_lock) = runtime_lock {
|
||||
spawn_next_game_creator_agent_background_task_drain_with_lock(
|
||||
root,
|
||||
&record.agent_id,
|
||||
runtime_lock,
|
||||
);
|
||||
if external_agent_runner_owns_background_execution() {
|
||||
spawn_next_game_creator_agent_background_task_drain_with_lock(
|
||||
root,
|
||||
&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
|
||||
|
||||
+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()
|
||||
.map(|result| result.state);
|
||||
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.source = source.trim().to_string();
|
||||
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.current_task == state.current_task;
|
||||
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.max_loop_iterations = previous_state.max_loop_iterations;
|
||||
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(),
|
||||
last_response: None,
|
||||
error: None,
|
||||
started_at: 0,
|
||||
updated_at: unix_timestamp(),
|
||||
}
|
||||
}
|
||||
@@ -2823,6 +2832,17 @@ pub(super) fn append_unique_game_creator_agent_runtime_pending_task(
|
||||
requested_run_profile: Option<&str>,
|
||||
task_link: Option<&AgentRuntimeTaskLink>,
|
||||
) -> 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 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(
|
||||
@@ -2865,6 +2885,8 @@ pub(super) fn append_unique_game_creator_agent_runtime_pending_task(
|
||||
updated_at: unix_timestamp(),
|
||||
};
|
||||
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) {
|
||||
let public_error = redact_agent_runtime_project_paths(root, &error, 500);
|
||||
let failed = AgentRuntimeTaskRecord {
|
||||
@@ -2882,7 +2904,7 @@ pub(super) fn append_unique_game_creator_agent_runtime_pending_task(
|
||||
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,
|
||||
agent_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) task_queue: AgentRuntimeTaskQueueSummary,
|
||||
pub(super) recent_tasks: Vec<AgentRuntimeTaskRecord>,
|
||||
pub(super) run_started_at: Option<u64>,
|
||||
}
|
||||
|
||||
pub(super) fn read_game_creator_agent_runtime_task_snapshot(
|
||||
path: &Path,
|
||||
) -> 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(
|
||||
path: &Path,
|
||||
session_id: Option<&str>,
|
||||
run_id: Option<&str>,
|
||||
) -> Result<AgentRuntimeTaskSnapshot, String> {
|
||||
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)
|
||||
.into_iter()
|
||||
.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 {
|
||||
task_queue,
|
||||
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(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
run_id: &str,
|
||||
input: &serde_json::Value,
|
||||
) -> AgentRuntimeToolObservation {
|
||||
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) {
|
||||
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(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
run_id: &str,
|
||||
input: &serde_json::Value,
|
||||
) -> AgentRuntimeToolObservation {
|
||||
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) {
|
||||
return agent_runtime_revision_advance_failure_observation(
|
||||
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 message = format!("来自 {agent_id} 的定向消息:{content}");
|
||||
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,
|
||||
};
|
||||
}
|
||||
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
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
.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());
|
||||
}
|
||||
|
||||
schedule_autonomous_game_build_ready_tasks_at(
|
||||
let scheduled_ready_tasks = schedule_autonomous_game_build_ready_tasks_at(
|
||||
root,
|
||||
¤t_task.agent_id,
|
||||
¤t_task.run_id,
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user