Files
Genarrative/apps/ai-game-creator-shell/src-tauri/src/goal.rs
T
lhk229 ec3a187dd7
Project CI / AI game creator shell Rust crates (push) Successful in 1m11s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m34s
Project CI / Backend tests (push) Successful in 4m46s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m31s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 9m41s
Project CI / Frontend tests (push) Successful in 2m19s
Project CI / Native shell tests (push) Successful in 7m5s
Project CI / AI game creator shell web tests (push) Successful in 1m54s
Project CI / Repository checks (push) Successful in 2m36s
处理编译大量warning问题 (#503)
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/503
Co-authored-by: Linghong <ink29535@proton.me>
Co-committed-by: Linghong <ink29535@proton.me>
2026-09-24 13:01:31 +08:00

1264 lines
46 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use super::*;
use sha2::{Digest, Sha256};
pub(crate) const AGENT_GOAL_SCHEMA_VERSION: &str = "game-creator-agent-goal.v1";
pub(crate) const AGENT_GOAL_STATUS_ACTIVE: &str = "active";
pub(crate) const AGENT_GOAL_STATUS_PAUSE_REQUESTED: &str = "pause-requested";
pub(crate) const AGENT_GOAL_STATUS_PAUSED: &str = "paused";
pub(crate) const AGENT_GOAL_STATUS_CLEARING: &str = "clearing";
pub(crate) const AGENT_GOAL_STATUS_CLEARED: &str = "cleared";
pub(crate) const AGENT_GOAL_STATUS_COMPLETED: &str = "completed";
pub(crate) const AGENT_GOAL_STATUS_NEEDS_RECONCILIATION: &str = "needs-reconciliation";
const AGENT_GOAL_SIDECAR_MAX_BYTES: usize = 64 * 1024;
const AGENT_GOAL_OUTCOME_MAX_CHARS: usize = 4_000;
const AGENT_GOAL_ITEM_MAX_CHARS: usize = 1_000;
const AGENT_GOAL_ITEM_LIMIT: usize = 8;
fn agent_goal_now_nanos() -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
}
fn agent_goal_path_key(value: &str) -> String {
let digest = format!("{:x}", Sha256::digest(value.as_bytes()));
digest.chars().take(32).collect()
}
fn agent_goal_current_relative_path(agent_id: &str, session_id: &str) -> String {
format!(
".agent/runtime/goals/current/{}/{}.json",
agent_goal_path_key(agent_id),
agent_goal_path_key(session_id)
)
}
fn agent_goal_history_relative_path(agent_id: &str, goal_id: &str) -> String {
format!(
".agent/runtime/goals/history/{}/{}.json",
agent_goal_path_key(agent_id),
agent_goal_path_key(goal_id)
)
}
fn normalize_agent_goal_text(value: &str, max_chars: usize, label: &str) -> Result<String, String> {
let value = value.trim();
if value.is_empty() {
return Err(format!("{label} 不能为空"));
}
if value.chars().count() > max_chars {
return Err(format!("{label} 超过 {max_chars} 字符上限"));
}
if value
.chars()
.any(|character| character.is_control() && !matches!(character, '\n' | '\r' | '\t'))
{
return Err(format!("{label} 不能包含控制字符"));
}
Ok(value.to_string())
}
fn normalize_agent_goal_items(values: Vec<String>, label: &str) -> Result<Vec<String>, String> {
if values.len() > AGENT_GOAL_ITEM_LIMIT {
return Err(format!("{label} 最多 {AGENT_GOAL_ITEM_LIMIT} 条"));
}
let mut normalized = Vec::with_capacity(values.len());
for (index, value) in values.into_iter().enumerate() {
let value = normalize_agent_goal_text(
&value,
AGENT_GOAL_ITEM_MAX_CHARS,
&format!("{label} #{}", index + 1),
)?;
if normalized.iter().any(|existing| existing == &value) {
return Err(format!("{label} 不能包含重复项"));
}
normalized.push(value);
}
Ok(normalized)
}
pub(crate) fn agent_goal_status_is_valid(status: &str) -> bool {
matches!(
status,
AGENT_GOAL_STATUS_ACTIVE
| AGENT_GOAL_STATUS_PAUSE_REQUESTED
| AGENT_GOAL_STATUS_PAUSED
| AGENT_GOAL_STATUS_CLEARING
| AGENT_GOAL_STATUS_CLEARED
| AGENT_GOAL_STATUS_COMPLETED
| AGENT_GOAL_STATUS_NEEDS_RECONCILIATION
)
}
fn agent_goal_is_terminal(goal: &AgentGoalRecord) -> bool {
matches!(
goal.status.as_str(),
AGENT_GOAL_STATUS_CLEARED | AGENT_GOAL_STATUS_COMPLETED
)
}
pub(crate) fn agent_goal_snapshot_fingerprint(goal: &AgentGoalRecord) -> String {
let payload = serde_json::json!({
"projectId": goal.project_id,
"goalId": goal.goal_id,
"agentId": goal.agent_id,
"sessionId": goal.session_id,
"runId": goal.run_id,
"revision": goal.revision,
"outcome": goal.outcome,
"constraints": goal.constraints,
"verification": goal.verification,
});
format!(
"{:x}",
Sha256::digest(serde_json::to_vec(&payload).unwrap_or_default())
)
}
pub(crate) fn agent_goal_snapshot_fingerprint_for_state_at(
root: &Path,
state: &AgentRuntimeState,
) -> Result<String, String> {
let Some(goal_id) = state.goal_id.as_deref() else {
return Ok(String::new());
};
let goal = read_game_creator_agent_goal_at(root, &state.agent_id, &state.session_id)?
.ok_or_else(|| "Agent Runtime 已绑定 Goal,但规范 sidecar 缺失".to_string())?;
if goal.goal_id != goal_id
|| goal.run_id != state.run_id
|| goal.revision != state.goal_revision
{
return Err("Agent Runtime Goal 快照身份或 revision 不匹配".to_string());
}
Ok(agent_goal_snapshot_fingerprint(&goal))
}
fn validate_agent_goal_record(root: &Path, goal: &AgentGoalRecord) -> Result<(), String> {
if goal.schema_version != AGENT_GOAL_SCHEMA_VERSION
|| goal.project_id != game_creator_agent_runtime_context_project_id(root)?
|| goal.goal_id.trim().is_empty()
|| goal.agent_id.trim().is_empty()
|| goal.session_id.trim().is_empty()
|| goal.run_id.trim().is_empty()
|| goal.revision == 0
|| !agent_goal_status_is_valid(&goal.status)
|| goal.created_at == 0
|| goal.updated_at == 0
{
return Err("Agent Goal 身份或状态无效".to_string());
}
normalize_agent_goal_text(&goal.outcome, AGENT_GOAL_OUTCOME_MAX_CHARS, "Goal outcome")?;
normalize_agent_goal_items(goal.constraints.clone(), "Goal constraints")?;
let verification = normalize_agent_goal_items(goal.verification.clone(), "Goal verification")?;
if verification.is_empty() {
return Err("Goal verification 不能为空".to_string());
}
if goal.completion_evidence.len() > AGENT_GOAL_ITEM_LIMIT
|| goal
.completion_evidence
.iter()
.any(|item| item.trim().is_empty() || item.chars().count() > AGENT_GOAL_ITEM_MAX_CHARS)
{
return Err("Agent Goal 完成证据无效".to_string());
}
if goal
.response_fingerprint
.as_deref()
.is_some_and(|fingerprint| {
fingerprint.len() != 64 || !fingerprint.bytes().all(|byte| byte.is_ascii_hexdigit())
})
{
return Err("Agent Goal 回复指纹无效".to_string());
}
if goal.status == AGENT_GOAL_STATUS_COMPLETED
&& (goal.completed_at.is_none()
|| goal.response_fingerprint.is_none()
|| goal.completion_evidence.is_empty())
{
return Err("已完成 Agent Goal 缺少系统完成证据".to_string());
}
if goal.status == AGENT_GOAL_STATUS_CLEARED && goal.cleared_at.is_none() {
return Err("已清理 Agent Goal 缺少清理时间".to_string());
}
Ok(())
}
fn write_agent_goal_record(root: &Path, goal: &AgentGoalRecord) -> Result<(), String> {
validate_agent_goal_record(root, goal)?;
write_agent_runtime_json_sidecar_with_max_bytes(
root,
&agent_goal_current_relative_path(&goal.agent_id, &goal.session_id),
"Agent Goal",
goal,
AGENT_GOAL_SIDECAR_MAX_BYTES,
)
}
fn archive_agent_goal_record(root: &Path, goal: &AgentGoalRecord) -> Result<(), String> {
validate_agent_goal_record(root, goal)?;
write_agent_runtime_json_sidecar_with_max_bytes(
root,
&agent_goal_history_relative_path(&goal.agent_id, &goal.goal_id),
"Agent Goal history",
goal,
AGENT_GOAL_SIDECAR_MAX_BYTES,
)
}
pub(crate) fn read_game_creator_agent_goal_at(
root: &Path,
agent_id: &str,
session_id: &str,
) -> Result<Option<AgentGoalRecord>, String> {
validate_project_root(root)?;
let agent_id = normalize_game_creator_runtime_agent_id(agent_id)?;
let session_id = session_id.trim();
if session_id.is_empty() {
return Err("Agent Goal sessionId 不能为空".to_string());
}
let goal = read_agent_runtime_json_sidecar_with_max_bytes::<AgentGoalRecord>(
root,
&agent_goal_current_relative_path(&agent_id, session_id),
"Agent Goal",
AGENT_GOAL_SIDECAR_MAX_BYTES,
)?;
if let Some(goal) = &goal {
validate_agent_goal_record(root, goal)?;
if goal.agent_id != agent_id || goal.session_id != session_id {
return Err("Agent Goal 与请求的 Agent/Session 身份不匹配".to_string());
}
}
Ok(goal)
}
pub(crate) fn hydrate_game_creator_agent_goal_state_at(
root: &Path,
state: &mut AgentRuntimeState,
) -> Result<Option<AgentGoalRecord>, String> {
if state.agent_id.trim().is_empty() || state.session_id.trim().is_empty() {
return Ok(None);
}
let goal = read_game_creator_agent_goal_at(root, &state.agent_id, &state.session_id)?;
let Some(goal) = goal else {
if state.goal_id.is_some() {
return Err("Agent Runtime 已绑定 Goal,但规范 Goal sidecar 缺失".to_string());
}
return Ok(None);
};
if goal.run_id != state.run_id {
if state.goal_id.as_deref() == Some(goal.goal_id.as_str()) {
return Err("Agent Runtime Goal runId 与规范 sidecar 不匹配".to_string());
}
return Ok(None);
}
if state
.goal_id
.as_deref()
.is_some_and(|goal_id| goal_id != goal.goal_id)
|| (state.goal_revision != 0 && state.goal_revision > goal.revision)
{
return Err("Agent Runtime Goal 身份或 revision 与规范 sidecar 冲突".to_string());
}
state.goal_id = Some(goal.goal_id.clone());
state.goal_revision = goal.revision;
state.goal_status = Some(goal.status.clone());
state.goal_outcome = Some(goal.outcome.clone());
state.goal_constraints = goal.constraints.clone();
state.goal_verification = goal.verification.clone();
state.current_goal = goal.outcome.clone();
Ok(Some(goal))
}
pub(crate) fn game_creator_agent_goal_task_binding_at(
root: &Path,
agent_id: &str,
session_id: &str,
run_id: &str,
) -> Result<(Option<String>, u64, Option<String>), String> {
let Some(goal) = read_game_creator_agent_goal_at(root, agent_id, session_id)? else {
return Ok((None, 0, None));
};
if goal.run_id != run_id {
return Ok((None, 0, None));
}
Ok((Some(goal.goal_id), goal.revision, Some(goal.status)))
}
pub(crate) fn ensure_game_creator_agent_goal_allows_run_at(
root: &Path,
agent_id: &str,
session_id: &str,
run_id: &str,
) -> Result<(), String> {
let Some(goal) = read_game_creator_agent_goal_at(root, agent_id, session_id)? else {
return Ok(());
};
if !agent_goal_is_terminal(&goal) && goal.run_id != run_id {
return Err(format!(
"当前 Session 已有未结束 Goal:goalId={} runId={} status={}",
goal.goal_id, goal.run_id, goal.status
));
}
Ok(())
}
fn new_agent_goal_id(project_id: &str, agent_id: &str, session_id: &str, run_id: &str) -> String {
let payload = format!(
"{project_id}\n{agent_id}\n{session_id}\n{run_id}\n{}",
agent_goal_now_nanos()
);
let fingerprint = format!("{:x}", Sha256::digest(payload.as_bytes()));
format!("goal-{}", fingerprint.chars().take(32).collect::<String>())
}
pub(crate) fn start_game_creator_agent_goal_at(
root: &Path,
agent_id: &str,
session_id: Option<&str>,
outcome: &str,
constraints: Vec<String>,
verification: Vec<String>,
requested_run_id: &str,
) -> Result<AgentGoalMutationResult, String> {
validate_project_root(root)?;
let agent_id = normalize_game_creator_runtime_agent_id(agent_id)?;
let outcome = normalize_agent_goal_text(outcome, AGENT_GOAL_OUTCOME_MAX_CHARS, "Goal outcome")?;
let constraints = normalize_agent_goal_items(constraints, "Goal constraints")?;
let mut verification = normalize_agent_goal_items(verification, "Goal verification")?;
if verification.is_empty() {
verification.push(outcome.clone());
}
let run_id = if requested_run_id.trim().is_empty() {
format!("goal-{agent_id}-{}", agent_goal_now_nanos())
} else {
normalize_game_creator_agent_runtime_run_id(&agent_id, requested_run_id)
};
let project_id = game_creator_agent_runtime_context_project_id(root)?;
let (runtime, queued_run_id, goal) = with_agent_conversation_session_lane_at(
root,
&agent_id,
"Agent Goal 与 Session Runtime 入队",
|| {
let session_id =
resolve_agent_conversation_session_id_at(root, &agent_id, session_id, true)?;
ensure_agent_session_has_no_live_tasks(root, &agent_id, &session_id)?;
let now = unix_timestamp();
let goal = AgentGoalRecord {
schema_version: AGENT_GOAL_SCHEMA_VERSION.to_string(),
project_id: project_id.clone(),
goal_id: new_agent_goal_id(&project_id, &agent_id, &session_id, &run_id),
agent_id: agent_id.clone(),
session_id: session_id.clone(),
run_id: run_id.clone(),
revision: 1,
status: AGENT_GOAL_STATUS_ACTIVE.to_string(),
outcome: outcome.clone(),
constraints: constraints.clone(),
verification: verification.clone(),
completion_evidence: Vec::new(),
response_fingerprint: None,
created_at: now,
pause_requested_at: None,
paused_at: None,
completed_at: None,
cleared_at: None,
error: None,
updated_at: now,
};
{
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
"runtime.goal.start",
)?;
if let Some(existing) =
read_game_creator_agent_goal_at(root, &agent_id, &session_id)?
{
if !agent_goal_is_terminal(&existing) {
return Err(format!(
"当前 Session 已有未结束 Goal:goalId={} status={}",
existing.goal_id, existing.status
));
}
archive_agent_goal_record(root, &existing)?;
}
write_agent_goal_record(root, &goal)?;
}
match start_game_creator_agent_goal_task_in_session_lane_at(
root,
&agent_id,
&session_id,
&outcome,
&run_id,
) {
Ok((runtime, queued_run_id)) if queued_run_id == run_id => {
Ok((runtime, queued_run_id, goal))
}
Ok((_runtime, queued_run_id)) => {
let error = format!(
"Goal Runtime runId 漂移:expected={run_id}, actual={queued_run_id}"
);
mark_game_creator_agent_goal_needs_reconciliation_at(root, &goal, &error)?;
Err(error)
}
Err(error) => {
mark_game_creator_agent_goal_paused_after_failure_at(root, &goal, &error)?;
Err(error)
}
}
},
)?;
notify_external_agent_runner_after_background_task_enqueue(
root,
&agent_id,
&goal.session_id,
&queued_run_id,
)?;
let _ = append_agent_db_record(
root,
serde_json::json!({
"recordType": "agent.runtime.goal.started",
"agentId": goal.agent_id,
"sessionId": goal.session_id,
"runId": goal.run_id,
"goalId": goal.goal_id,
"goalRevision": goal.revision,
"outcomeSha256": format!("{:x}", Sha256::digest(goal.outcome.as_bytes())),
"constraintCount": goal.constraints.len(),
"verificationCount": goal.verification.len(),
}),
);
Ok(AgentGoalMutationResult {
goal,
runtime,
provider_interrupted: false,
})
}
fn update_agent_goal_record_at_locked<F>(
root: &Path,
agent_id: &str,
session_id: &str,
expected_goal_id: &str,
expected_revision: u64,
update: F,
) -> Result<AgentGoalRecord, String>
where
F: FnOnce(&mut AgentGoalRecord) -> Result<(), String>,
{
let mut goal = read_game_creator_agent_goal_at(root, agent_id, session_id)?
.ok_or_else(|| "当前 Session 没有 Agent Goal".to_string())?;
if goal.goal_id != expected_goal_id || goal.revision != expected_revision {
return Err(format!(
"Agent Goal 身份或 revision 已变化:goalId={} revision={}",
goal.goal_id, goal.revision
));
}
update(&mut goal)?;
goal.updated_at = unix_timestamp();
write_agent_goal_record(root, &goal)?;
Ok(goal)
}
pub(crate) fn edit_game_creator_agent_goal_at(
root: &Path,
agent_id: &str,
session_id: &str,
goal_id: &str,
expected_revision: u64,
outcome: &str,
constraints: Vec<String>,
verification: Vec<String>,
) -> Result<AgentGoalMutationResult, String> {
let agent_id = normalize_game_creator_runtime_agent_id(agent_id)?;
let outcome = normalize_agent_goal_text(outcome, AGENT_GOAL_OUTCOME_MAX_CHARS, "Goal outcome")?;
let constraints = normalize_agent_goal_items(constraints, "Goal constraints")?;
let mut verification = normalize_agent_goal_items(verification, "Goal verification")?;
if verification.is_empty() {
verification.push(outcome.clone());
}
let (goal, changed, prepared_finalization) = {
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
"runtime.goal.edit",
)?;
let current = read_game_creator_agent_goal_at(root, &agent_id, session_id)?
.ok_or_else(|| "当前 Session 没有 Agent Goal".to_string())?;
if current.goal_id != goal_id || current.revision != expected_revision {
return Err(format!(
"Agent Goal 身份或 revision 已变化:goalId={} revision={}",
current.goal_id, current.revision
));
}
if agent_goal_is_terminal(&current)
|| current.status == AGENT_GOAL_STATUS_CLEARING
|| current.status == AGENT_GOAL_STATUS_NEEDS_RECONCILIATION
{
return Err(format!("当前 Goal 状态不能编辑:{}", current.status));
}
if current.outcome == outcome
&& current.constraints == constraints
&& current.verification == verification
{
(current, false, false)
} else {
let prepared_finalization = if let Some(journal) =
read_game_creator_agent_runtime_finalization_journal(
root,
&agent_id,
&current.run_id,
)? {
if game_creator_agent_runtime_finalization_assistant_exists(root, &journal)? {
return Err("当前 Goal 的 assistant 最终回复已经持久化,不能再编辑".to_string());
}
if journal.status != AGENT_RUNTIME_FINALIZATION_STATUS_PREPARED {
return Err("当前 Goal finalization 状态需要人工核对,不能编辑".to_string());
}
true
} else {
false
};
let updated = update_agent_goal_record_at_locked(
root,
&agent_id,
session_id,
goal_id,
expected_revision,
|goal| {
if agent_goal_is_terminal(goal)
|| goal.status == AGENT_GOAL_STATUS_CLEARING
|| goal.status == AGENT_GOAL_STATUS_NEEDS_RECONCILIATION
{
return Err(format!("当前 Goal 状态不能编辑:{}", goal.status));
}
if goal.outcome == outcome
&& goal.constraints == constraints
&& goal.verification == verification
{
return Ok(());
}
goal.outcome = outcome.clone();
goal.constraints = constraints.clone();
goal.verification = verification.clone();
goal.revision = goal
.revision
.checked_add(1)
.ok_or_else(|| "Agent Goal revision 已达上限".to_string())?;
goal.completion_evidence.clear();
goal.response_fingerprint = None;
goal.completed_at = None;
goal.error = None;
Ok(())
},
)?;
(updated, true, prepared_finalization)
}
};
if !changed {
let runtime =
read_game_creator_agent_runtime_for_session_at(root, &agent_id, Some(session_id))?;
return Ok(AgentGoalMutationResult {
goal,
runtime,
provider_interrupted: false,
});
}
let instruction = render_agent_goal_edit_instruction(&goal);
let mut provider_interrupted = false;
let runtime_before =
read_game_creator_agent_runtime_for_session_at(root, &agent_id, Some(session_id))?;
if prepared_finalization {
if external_agent_runner_enabled() && !external_agent_runner_is_server_process() {
wake_external_agent_runner_pending_for_run(
root,
&agent_id,
&goal.run_id,
runtime_before.state.loop_iteration,
)?;
} else {
let _ = resume_game_creator_agent_background_tasks_at(root)?;
}
} else if goal.status == AGENT_GOAL_STATUS_ACTIVE
&& runtime_before.state.run_id == goal.run_id
&& matches!(
runtime_before.state.status.as_str(),
"running" | "waiting-for-confirmation" | "waiting-for-user-input"
)
{
if runtime_before.state.status == "waiting-for-user-input" {
if external_agent_runner_enabled() && !external_agent_runner_is_server_process() {
wake_external_agent_runner_pending_for_run(
root,
&agent_id,
&goal.run_id,
runtime_before.state.loop_iteration,
)?;
} else {
let _ = resume_game_creator_agent_background_tasks_at(root)?;
}
} else {
let steer_id = format!("goal-edit-{}-{}", goal.goal_id, goal.revision);
let steer = steer_game_creator_agent_runtime_task_at(
root,
&agent_id,
session_id,
&goal.run_id,
&steer_id,
&instruction,
"goal-edit",
)?;
provider_interrupted = steer.provider_interrupted;
if external_agent_runner_enabled()
&& !external_agent_runner_is_server_process()
&& !provider_interrupted
{
provider_interrupted =
steer_external_agent_runner(root, &agent_id, &goal.run_id, &steer_id)?;
}
if runtime_before.state.status == "waiting-for-confirmation" {
if external_agent_runner_enabled() && !external_agent_runner_is_server_process() {
wake_external_agent_runner_pending_for_run(
root,
&agent_id,
&goal.run_id,
runtime_before.state.loop_iteration,
)?;
} else {
let _ = resume_game_creator_agent_background_tasks_at(root)?;
}
}
}
} else if runtime_before.state.run_id == goal.run_id {
refresh_game_creator_agent_goal_runtime_projection_at(root, &goal)?;
}
let runtime =
read_game_creator_agent_runtime_for_session_at(root, &agent_id, Some(session_id))?;
Ok(AgentGoalMutationResult {
goal,
runtime,
provider_interrupted,
})
}
pub(crate) fn pause_game_creator_agent_goal_at(
root: &Path,
agent_id: &str,
session_id: &str,
goal_id: &str,
expected_revision: u64,
) -> Result<AgentGoalMutationResult, String> {
let agent_id = normalize_game_creator_runtime_agent_id(agent_id)?;
let (goal, already_paused) = {
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
"runtime.goal.pause",
)?;
let mut already_paused = false;
let goal = update_agent_goal_record_at_locked(
root,
&agent_id,
session_id,
goal_id,
expected_revision,
|goal| {
if goal.status == AGENT_GOAL_STATUS_PAUSED {
already_paused = true;
return Ok(());
}
if goal.status == AGENT_GOAL_STATUS_PAUSE_REQUESTED {
return Ok(());
}
if goal.status != AGENT_GOAL_STATUS_ACTIVE {
return Err(format!("当前 Goal 状态不能暂停:{}", goal.status));
}
goal.status = AGENT_GOAL_STATUS_PAUSE_REQUESTED.to_string();
goal.pause_requested_at = Some(unix_timestamp());
goal.error = None;
Ok(())
},
)?;
(goal, already_paused)
};
if already_paused {
let runtime =
read_game_creator_agent_runtime_for_session_at(root, &agent_id, Some(session_id))?;
return Ok(AgentGoalMutationResult {
goal,
runtime,
provider_interrupted: false,
});
}
let provider_interrupted =
if external_agent_runner_enabled() && !external_agent_runner_is_server_process() {
pause_external_agent_runner(root, &agent_id, &goal.run_id)?
} else {
interrupt_game_creator_agent_runtime_provider_request_at(root, &agent_id, &goal.run_id)?
};
let runtime = pause_game_creator_agent_runtime_for_goal_at(root, &goal)?;
let goal = read_game_creator_agent_goal_at(root, &agent_id, session_id)?
.ok_or_else(|| "暂停后 Agent Goal sidecar 缺失".to_string())?;
Ok(AgentGoalMutationResult {
goal,
runtime,
provider_interrupted,
})
}
pub(crate) fn resume_game_creator_agent_goal_at(
root: &Path,
agent_id: &str,
session_id: &str,
goal_id: &str,
expected_revision: u64,
) -> Result<AgentGoalMutationResult, String> {
let agent_id = normalize_game_creator_runtime_agent_id(agent_id)?;
let goal = {
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
"runtime.goal.resume",
)?;
update_agent_goal_record_at_locked(
root,
&agent_id,
session_id,
goal_id,
expected_revision,
|goal| {
if goal.status == AGENT_GOAL_STATUS_ACTIVE {
return Ok(());
}
if goal.status != AGENT_GOAL_STATUS_PAUSED {
return Err(format!("当前 Goal 状态不能恢复:{}", goal.status));
}
goal.status = AGENT_GOAL_STATUS_ACTIVE.to_string();
goal.pause_requested_at = None;
goal.paused_at = None;
goal.error = None;
Ok(())
},
)?
};
remove_game_creator_agent_runtime_cancel_request(root, &agent_id, &goal.run_id);
let runtime = resume_game_creator_agent_runtime_for_goal_at(root, &goal)?;
if external_agent_runner_enabled() && !external_agent_runner_is_server_process() {
wake_external_agent_runner_pending_for_run(
root,
&agent_id,
&goal.run_id,
runtime.state.loop_iteration,
)?;
} else {
let _ = resume_game_creator_agent_background_tasks_at(root)?;
}
let runtime =
read_game_creator_agent_runtime_for_session_at(root, &agent_id, Some(session_id))?;
Ok(AgentGoalMutationResult {
goal,
runtime,
provider_interrupted: false,
})
}
pub(crate) fn clear_game_creator_agent_goal_at(
root: &Path,
agent_id: &str,
session_id: &str,
goal_id: &str,
expected_revision: u64,
) -> Result<AgentGoalMutationResult, String> {
let agent_id = normalize_game_creator_runtime_agent_id(agent_id)?;
let goal = {
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
"runtime.goal.clear",
)?;
let current = read_game_creator_agent_goal_at(root, &agent_id, session_id)?
.ok_or_else(|| "当前 Session 没有 Agent Goal".to_string())?;
if current.goal_id != goal_id || current.revision != expected_revision {
return Err(format!(
"Agent Goal 身份或 revision 已变化:goalId={} revision={}",
current.goal_id, current.revision
));
}
if !agent_goal_is_terminal(&current) {
if let Some(journal) = read_game_creator_agent_runtime_finalization_journal(
root,
&agent_id,
&current.run_id,
)? {
if game_creator_agent_runtime_finalization_assistant_exists(root, &journal)? {
return Err("当前 Goal 的 assistant 最终回复已经持久化,不能清理".to_string());
}
}
}
update_agent_goal_record_at_locked(
root,
&agent_id,
session_id,
goal_id,
expected_revision,
|goal| {
if goal.status == AGENT_GOAL_STATUS_CLEARED {
return Ok(());
}
if goal.status == AGENT_GOAL_STATUS_COMPLETED {
goal.status = AGENT_GOAL_STATUS_CLEARED.to_string();
goal.cleared_at = Some(unix_timestamp());
return Ok(());
}
if goal.status == AGENT_GOAL_STATUS_NEEDS_RECONCILIATION {
return Err("需要先核对 Goal/Runtime 身份,不能直接清理".to_string());
}
goal.status = AGENT_GOAL_STATUS_CLEARING.to_string();
goal.error = None;
Ok(())
},
)?
};
let mut provider_interrupted = false;
let runtime = if goal.status == AGENT_GOAL_STATUS_CLEARED {
refresh_game_creator_agent_goal_runtime_projection_at(root, &goal)?
} else if external_agent_runner_enabled() && !external_agent_runner_is_server_process() {
provider_interrupted = cancel_external_agent_runner_goal(root, &agent_id, &goal.run_id)?;
read_game_creator_agent_runtime_for_session_at(root, &agent_id, Some(session_id))?
} else {
write_game_creator_agent_runtime_cancel_request(
root,
&agent_id,
&goal.run_id,
"开发者清理持久 Goal",
)?;
provider_interrupted = interrupt_game_creator_agent_runtime_provider_request_at(
root,
&agent_id,
&goal.run_id,
)?;
cancel_game_creator_agent_runtime_task_at(root, &agent_id, &goal.run_id)?
};
let goal = read_game_creator_agent_goal_at(root, &agent_id, session_id)?
.ok_or_else(|| "清理后 Agent Goal sidecar 缺失".to_string())?;
Ok(AgentGoalMutationResult {
goal,
runtime,
provider_interrupted,
})
}
pub(crate) fn render_agent_goal_prompt_context_at(
root: &Path,
agent_id: &str,
session_id: &str,
run_id: &str,
) -> Result<String, String> {
let Some(goal) = read_game_creator_agent_goal_at(root, agent_id, session_id)? else {
return Ok(String::new());
};
if goal.run_id != run_id || goal.status == AGENT_GOAL_STATUS_CLEARED {
return Ok(String::new());
}
let constraints = if goal.constraints.is_empty() {
prompt_text!("goalContext.empty_constraint_list").to_string()
} else {
goal.constraints
.iter()
.map(|item| format!("- {item}"))
.collect::<Vec<_>>()
.join("\n")
};
let verification = goal
.verification
.iter()
.map(|item| format!("- {item}"))
.collect::<Vec<_>>()
.join("\n");
Ok(format!(
prompt_text!("goalContext.persistent_goal"),
goal.goal_id, goal.revision, goal.status, goal.outcome, constraints, verification
))
}
fn render_agent_goal_edit_instruction(goal: &AgentGoalRecord) -> String {
let constraints = if goal.constraints.is_empty() {
prompt_text!("goalContext.empty_constraints").to_string()
} else {
goal.constraints.join(";")
};
format!(
prompt_text!("goalContext.goal_edit"),
goal.revision,
goal.outcome,
constraints,
goal.verification.join(";")
)
}
pub(crate) fn game_creator_agent_goal_completion_blocker_at_locked(
root: &Path,
state: &AgentRuntimeState,
) -> Option<AgentRuntimeToolObservation> {
let goal = match read_game_creator_agent_goal_at(root, &state.agent_id, &state.session_id) {
Ok(Some(goal)) => goal,
Ok(None) => {
return state.goal_id.as_ref().map(|_| {
agent_runtime_goal_blocker(
"Goal sidecar 缺失",
"Runtime 已绑定 Goal,但规范记录不存在;禁止完成当前 run。".to_string(),
)
});
}
Err(error) => {
return Some(agent_runtime_goal_blocker("Goal sidecar 无法读取", error));
}
};
let Some(goal_id) = state.goal_id.as_deref() else {
return (goal.run_id == state.run_id).then(|| {
agent_runtime_goal_blocker(
"Runtime 缺少 Goal 绑定",
format!(
"当前 Agent/Session/run 存在 Goal:goalId={} revision={};禁止按无 Goal 任务完成。",
goal.goal_id, goal.revision
),
)
});
};
if goal.goal_id != goal_id
|| goal.run_id != state.run_id
|| goal.revision != state.goal_revision
{
return Some(agent_runtime_goal_blocker(
"Goal 身份或 revision 已变化",
format!(
"stateGoalId={} stateRevision={} currentGoalId={} currentRevision={};旧回复必须丢弃并重规划。",
goal_id, state.goal_revision, goal.goal_id, goal.revision
),
));
}
if goal.status != AGENT_GOAL_STATUS_ACTIVE {
return Some(agent_runtime_goal_blocker(
"Goal 当前不能完成",
format!(
"status={};暂停、清理或待核对状态不能写最终回复。",
goal.status
),
));
}
None
}
fn agent_runtime_goal_blocker(summary: &str, detail: String) -> AgentRuntimeToolObservation {
AgentRuntimeToolObservation {
tool: "runtime.goal".to_string(),
status: "blocked".to_string(),
summary: summary.to_string(),
detail: Some(detail),
}
}
pub(crate) fn mark_game_creator_agent_goal_paused_for_runtime_at_locked(
root: &Path,
state: &AgentRuntimeState,
) -> Result<Option<AgentGoalRecord>, String> {
let Some(goal_id) = state.goal_id.as_deref() else {
return Ok(None);
};
let mut goal = read_game_creator_agent_goal_at(root, &state.agent_id, &state.session_id)?
.ok_or_else(|| "暂停 Runtime 时 Agent Goal sidecar 缺失".to_string())?;
if goal.goal_id != goal_id
|| goal.run_id != state.run_id
|| goal.revision != state.goal_revision
{
return Err("暂停 Runtime 时 Agent Goal 身份或 revision 冲突".to_string());
}
if goal.status == AGENT_GOAL_STATUS_PAUSED {
return Ok(Some(goal));
}
if goal.status != AGENT_GOAL_STATUS_PAUSE_REQUESTED {
return Err(format!("暂停 Runtime 时 Goal 状态无效:{}", goal.status));
}
goal.status = AGENT_GOAL_STATUS_PAUSED.to_string();
goal.paused_at = Some(unix_timestamp());
goal.updated_at = unix_timestamp();
write_agent_goal_record(root, &goal)?;
Ok(Some(goal))
}
pub(crate) fn mark_game_creator_agent_goal_paused_for_runtime_at(
root: &Path,
state: &AgentRuntimeState,
) -> Result<Option<AgentGoalRecord>, String> {
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
"runtime.goal.paused",
)?;
mark_game_creator_agent_goal_paused_for_runtime_at_locked(root, state)
}
pub(crate) fn mark_game_creator_agent_goal_cleared_for_runtime_at_locked(
root: &Path,
state: &AgentRuntimeState,
) -> Result<Option<AgentGoalRecord>, String> {
let Some(goal_id) = state.goal_id.as_deref() else {
return Ok(None);
};
let mut goal = read_game_creator_agent_goal_at(root, &state.agent_id, &state.session_id)?
.ok_or_else(|| "清理 Runtime 时 Agent Goal sidecar 缺失".to_string())?;
if goal.goal_id != goal_id
|| goal.run_id != state.run_id
|| goal.revision != state.goal_revision
{
return Err("清理 Runtime 时 Agent Goal 身份或 revision 冲突".to_string());
}
if goal.status == AGENT_GOAL_STATUS_CLEARED {
return Ok(Some(goal));
}
if matches!(
goal.status.as_str(),
AGENT_GOAL_STATUS_ACTIVE | AGENT_GOAL_STATUS_PAUSE_REQUESTED
) {
goal.status = AGENT_GOAL_STATUS_PAUSED.to_string();
goal.pause_requested_at = None;
goal.paused_at = Some(unix_timestamp());
goal.error = Some("所属 Runtime 已取消;可显式恢复同一 Goal。".to_string());
goal.updated_at = unix_timestamp();
write_agent_goal_record(root, &goal)?;
return Ok(Some(goal));
}
if goal.status != AGENT_GOAL_STATUS_CLEARING {
return Ok(Some(goal));
}
goal.status = AGENT_GOAL_STATUS_CLEARED.to_string();
goal.cleared_at = Some(unix_timestamp());
goal.updated_at = unix_timestamp();
write_agent_goal_record(root, &goal)?;
Ok(Some(goal))
}
pub(crate) fn complete_game_creator_agent_goal_for_runtime_at_locked(
root: &Path,
state: &mut AgentRuntimeState,
response: &str,
) -> Result<Option<AgentGoalRecord>, String> {
let Some(goal_id) = state.goal_id.as_deref() else {
return Ok(None);
};
let mut goal = read_game_creator_agent_goal_at(root, &state.agent_id, &state.session_id)?
.ok_or_else(|| "完成 Runtime 时 Agent Goal sidecar 缺失".to_string())?;
if goal.goal_id != goal_id
|| goal.run_id != state.run_id
|| goal.revision != state.goal_revision
{
return Err("完成 Runtime 时 Agent Goal 身份或 revision 冲突".to_string());
}
let response_fingerprint = format!("{:x}", Sha256::digest(response.trim().as_bytes()));
if goal.status == AGENT_GOAL_STATUS_COMPLETED {
if goal.response_fingerprint.as_deref() != Some(response_fingerprint.as_str()) {
return Err("已完成 Agent Goal 的回复指纹冲突".to_string());
}
state.goal_status = Some(goal.status.clone());
return Ok(Some(goal));
}
if goal.status != AGENT_GOAL_STATUS_ACTIVE {
return Err(format!("当前 Agent Goal 状态不能完成:{}", goal.status));
}
let gate =
read_game_creator_agent_runtime_verification_gate(root, &state.agent_id, &state.run_id)?;
let completed_steps = state
.plan_steps
.iter()
.filter(|step| step.status == AGENT_RUNTIME_PLAN_STATUS_COMPLETED)
.count();
let mut completion_evidence = vec![
format!("goalRevision={}", goal.revision),
format!(
"planRevision={} completedSteps={completed_steps}",
state.plan_revision
),
format!(
"verificationRequired={} verifiedRevision={}",
gate.requires_verification,
gate.verified_revision
.map(|revision| revision.to_string())
.unwrap_or_else(|| "none".to_string())
),
format!("runId={} sessionId={}", state.run_id, state.session_id),
];
if state.agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
if let Some(blocker) = goal_contract_acceptance_completion_blocker_at_locked(root, state) {
return Err(format!(
"Agent Goal 的动态验收图尚未完成:{}",
blocker.summary()
));
}
if let Some(contract) =
read_game_creator_agent_runtime_goal_contract_at(root, &state.agent_id, &state.run_id)?
{
let acceptance = read_game_creator_agent_runtime_acceptance_graph_at(
root,
&state.agent_id,
&state.run_id,
)?
.ok_or_else(|| "Agent Goal 完成时缺少 Acceptance Graph 冻结快照".to_string())?;
completion_evidence.push(format!(
"goalContractFingerprint={}",
contract.contract_fingerprint
));
completion_evidence.push(format!(
"acceptanceRevision={} acceptanceStateFingerprint={}",
acceptance.revision, acceptance.state_fingerprint
));
}
}
goal.completion_evidence = completion_evidence;
goal.response_fingerprint = Some(response_fingerprint);
goal.status = AGENT_GOAL_STATUS_COMPLETED.to_string();
goal.completed_at = Some(unix_timestamp());
goal.updated_at = unix_timestamp();
goal.error = None;
write_agent_goal_record(root, &goal)?;
state.goal_status = Some(goal.status.clone());
Ok(Some(goal))
}
#[cfg(test)]
pub(crate) fn seed_game_creator_agent_goal_for_runtime_test_at(
root: &Path,
state: &mut AgentRuntimeState,
outcome: &str,
status: &str,
) -> Result<AgentGoalRecord, String> {
if !agent_goal_status_is_valid(status) {
return Err(format!("测试 Agent Goal 状态无效:{status}"));
}
let project_id = game_creator_agent_runtime_context_project_id(root)?;
let now = unix_timestamp();
let goal = AgentGoalRecord {
schema_version: AGENT_GOAL_SCHEMA_VERSION.to_string(),
project_id: project_id.clone(),
goal_id: new_agent_goal_id(
&project_id,
&state.agent_id,
&state.session_id,
&state.run_id,
),
agent_id: state.agent_id.clone(),
session_id: state.session_id.clone(),
run_id: state.run_id.clone(),
revision: 1,
status: status.to_string(),
outcome: normalize_agent_goal_text(outcome, AGENT_GOAL_OUTCOME_MAX_CHARS, "Goal outcome")?,
constraints: Vec::new(),
verification: vec![outcome.to_string()],
completion_evidence: Vec::new(),
response_fingerprint: None,
created_at: now,
pause_requested_at: (status == AGENT_GOAL_STATUS_PAUSE_REQUESTED).then_some(now),
paused_at: (status == AGENT_GOAL_STATUS_PAUSED).then_some(now),
completed_at: None,
cleared_at: None,
error: None,
updated_at: now,
};
{
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
"runtime.goal.test_seed",
)?;
write_agent_goal_record(root, &goal)?;
}
hydrate_game_creator_agent_goal_state_at(root, state)?;
state.updated_at = unix_timestamp();
append_game_creator_agent_runtime_task(root, state)?;
write_game_creator_agent_runtime_state(root, state)?;
Ok(goal)
}
#[cfg(test)]
pub(crate) fn revise_game_creator_agent_goal_for_runtime_test_at(
root: &Path,
state: &mut AgentRuntimeState,
outcome: &str,
) -> Result<AgentGoalRecord, String> {
let outcome = normalize_agent_goal_text(outcome, AGENT_GOAL_OUTCOME_MAX_CHARS, "Goal outcome")?;
let goal = {
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
"runtime.goal.test_revise",
)?;
let goal_id = state
.goal_id
.as_deref()
.ok_or_else(|| "测试 Runtime 未绑定 Agent Goal".to_string())?;
update_agent_goal_record_at_locked(
root,
&state.agent_id,
&state.session_id,
goal_id,
state.goal_revision,
|goal| {
goal.outcome = outcome.clone();
goal.verification = vec![outcome.clone()];
goal.revision = goal
.revision
.checked_add(1)
.ok_or_else(|| "Agent Goal revision 已达上限".to_string())?;
Ok(())
},
)?
};
hydrate_game_creator_agent_goal_state_at(root, state)?;
state.updated_at = unix_timestamp();
append_game_creator_agent_runtime_task(root, state)?;
write_game_creator_agent_runtime_state(root, state)?;
Ok(goal)
}
fn mark_game_creator_agent_goal_paused_after_failure_at(
root: &Path,
original: &AgentGoalRecord,
error: &str,
) -> Result<(), String> {
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
"runtime.goal.start_failed",
)?;
let mut goal = read_game_creator_agent_goal_at(root, &original.agent_id, &original.session_id)?
.ok_or_else(|| "启动失败后 Agent Goal sidecar 缺失".to_string())?;
if goal.goal_id != original.goal_id || goal.revision != original.revision {
return Err("启动失败后 Agent Goal 身份已变化".to_string());
}
goal.status = AGENT_GOAL_STATUS_PAUSED.to_string();
goal.paused_at = Some(unix_timestamp());
goal.error = Some(normalize_agent_goal_text(
error,
AGENT_GOAL_ITEM_MAX_CHARS,
"Goal error",
)?);
goal.updated_at = unix_timestamp();
write_agent_goal_record(root, &goal)
}
fn mark_game_creator_agent_goal_needs_reconciliation_at(
root: &Path,
original: &AgentGoalRecord,
error: &str,
) -> Result<(), String> {
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
"runtime.goal.reconciliation",
)?;
let mut goal = read_game_creator_agent_goal_at(root, &original.agent_id, &original.session_id)?
.ok_or_else(|| "Goal reconciliation sidecar 缺失".to_string())?;
if goal.goal_id != original.goal_id || goal.revision != original.revision {
return Err("Goal reconciliation 身份已变化".to_string());
}
goal.status = AGENT_GOAL_STATUS_NEEDS_RECONCILIATION.to_string();
goal.error = Some(normalize_agent_goal_text(
error,
AGENT_GOAL_ITEM_MAX_CHARS,
"Goal error",
)?);
goal.updated_at = unix_timestamp();
write_agent_goal_record(root, &goal)
}