完善自主构建正式产物与终端测试
新增终端 AppData 配置向导并强化跨平台密钥与进程树安全 升级自主构建为十六任务产物 DAG 并接入两类真实画布素材 完善完成基线、并发补验、验证证据与受限画布返工合同 强化确定性与真实 Swarm E2E 的 exactly-once 和正式产物校验 修复确定性 Provider 终态修复后的复验交付状态 同步更新客户端实现计划、决策记录和踩坑说明
This commit is contained in:
@@ -12,7 +12,9 @@
|
||||
"agent-task": "node scripts/run-cli-with-config.mjs --agent-task",
|
||||
"chat": "node scripts/run-cli-with-config.mjs --swarm-chat",
|
||||
"swarm": "node scripts/run-cli-with-config.mjs --swarm-chat",
|
||||
"test:chat": "node scripts/agent-swarm-test-chat.mjs",
|
||||
"config": "node scripts/game-creator-config-wizard.mjs",
|
||||
"test:chat": "node scripts/agent-swarm-test-chat.mjs --task \"制作一个可直接试玩的原创植物塔防小游戏:玩家选择并放置原创守卫阻挡敌人,完成波次后可以进入下一关并重新开始。主题、单位名称与视觉语言必须原创,不使用任何现有游戏角色、单位名、Logo 或受保护视觉语言。请自主完成正式产物、静态检查和双视口试玩验证。\" --no-open",
|
||||
"test:chat:manual": "node scripts/agent-swarm-test-chat.mjs",
|
||||
"agent-run": "node scripts/run-cli-with-config.mjs --agent-run",
|
||||
"agent-run:smoke": "node scripts/smoke-agent-run-local-provider.mjs",
|
||||
"agent-runtime:real-e2e": "node scripts/agent-runtime-real-e2e.mjs",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -246,7 +246,7 @@ export const runProfileBindingSchemaVersion =
|
||||
'game-creator-run-profile-binding.v1';
|
||||
|
||||
export const autonomousCompletionContractSchemaVersion =
|
||||
'game-creator-autonomous-completion-contract.v1';
|
||||
'game-creator-autonomous-completion-contract.v2';
|
||||
|
||||
export const autonomousPlaytestReceiptSchemaVersion =
|
||||
'game-creator-autonomous-playtest-receipt.v1';
|
||||
|
||||
+8
@@ -1028,6 +1028,7 @@ export async function validateSupervisorAutonomousPlayableEvidence(
|
||||
taskSha256: contract.taskSha256,
|
||||
baselineRevision: contract.baselineRevision,
|
||||
baselineIndexSha256: contract.baselineIndexSha256,
|
||||
baselineArtifacts: contract.baselineArtifacts,
|
||||
playtestScenario: contract.playtestScenario,
|
||||
createdAt: contract.createdAt,
|
||||
};
|
||||
@@ -1040,6 +1041,13 @@ export async function validateSupervisorAutonomousPlayableEvidence(
|
||||
contract.taskSha256 === hashValue(task) &&
|
||||
contract.baselineIndexSha256 ===
|
||||
state.supervisorAutonomousPlayable.initialGameIndexSha256 &&
|
||||
Array.isArray(contract.baselineArtifacts) &&
|
||||
contract.baselineArtifacts.length === 1 &&
|
||||
contract.baselineArtifacts[0]?.path === 'game/index.html' &&
|
||||
contract.baselineArtifacts[0]?.sha256 ===
|
||||
state.supervisorAutonomousPlayable.initialGameIndexSha256 &&
|
||||
Number.isInteger(contract.baselineArtifacts[0]?.sizeBytes) &&
|
||||
contract.baselineArtifacts[0].sizeBytes > 0 &&
|
||||
contract.playtestScenario === 'lane-defense-v1' &&
|
||||
contract.contractFingerprint === hashJsonValue(contractIdentity),
|
||||
'supervisor-autonomous-playable-completion-contract-invalid',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,18 @@ import { spawn } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { resolveGameCreatorAppConfigDir } from './game-creator-config-wizard.mjs';
|
||||
|
||||
const appRoot = path.resolve(fileURLToPath(new URL('..', import.meta.url)));
|
||||
const cliArguments = process.argv.slice(2);
|
||||
const hasConfigDir = cliArguments.some(
|
||||
(argument, index) =>
|
||||
argument === '--config-dir' ||
|
||||
(index > 0 && cliArguments[index - 1] === '--config-dir'),
|
||||
);
|
||||
const runtimeConfigArguments = hasConfigDir
|
||||
? []
|
||||
: ['--config-dir', resolveGameCreatorAppConfigDir()];
|
||||
|
||||
const cargo = process.platform === 'win32' ? 'cargo.exe' : 'cargo';
|
||||
const child = spawn(
|
||||
@@ -12,7 +23,8 @@ const child = spawn(
|
||||
'--manifest-path',
|
||||
'src-tauri/Cargo.toml',
|
||||
'--',
|
||||
...process.argv.slice(2),
|
||||
...runtimeConfigArguments,
|
||||
...cliArguments,
|
||||
],
|
||||
{
|
||||
cwd: appRoot,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -121,7 +121,8 @@ pub(crate) fn append_agent_runtime_action_receipt(
|
||||
if !is_terminal_agent_runtime_action_status(&observation.status) {
|
||||
return Err("Agent 持久动作回执只能记录终态 observation".to_string());
|
||||
}
|
||||
let safe_detail = agent_runtime_action_receipt_safe_detail(root, observation);
|
||||
let safe_detail =
|
||||
agent_runtime_action_receipt_safe_detail_for_owner(root, &agent_id, &run_id, observation);
|
||||
let detail_unavailable = observation.detail.is_some() && safe_detail.is_none();
|
||||
let input_summary = agent_runtime_public_action_input_summary(root, &tool, input_summary);
|
||||
let summary = agent_runtime_action_receipt_safe_text(
|
||||
@@ -161,6 +162,27 @@ pub(crate) fn append_agent_runtime_action_receipt(
|
||||
pub(in crate::agent) fn agent_runtime_action_receipt_safe_detail(
|
||||
root: &Path,
|
||||
observation: &AgentRuntimeToolObservation,
|
||||
) -> Option<String> {
|
||||
agent_runtime_action_receipt_safe_detail_with_owner(root, None, observation)
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn agent_runtime_action_receipt_safe_detail_for_owner(
|
||||
root: &Path,
|
||||
receipt_agent_id: &str,
|
||||
receipt_run_id: &str,
|
||||
observation: &AgentRuntimeToolObservation,
|
||||
) -> Option<String> {
|
||||
agent_runtime_action_receipt_safe_detail_with_owner(
|
||||
root,
|
||||
Some((receipt_agent_id, receipt_run_id)),
|
||||
observation,
|
||||
)
|
||||
}
|
||||
|
||||
fn agent_runtime_action_receipt_safe_detail_with_owner(
|
||||
root: &Path,
|
||||
receipt_owner: Option<(&str, &str)>,
|
||||
observation: &AgentRuntimeToolObservation,
|
||||
) -> Option<String> {
|
||||
if observation.tool == GAME_CREATOR_USER_INPUT_REQUEST_TOOL {
|
||||
return game_creator_agent_user_input_public_observation_metadata(
|
||||
@@ -300,6 +322,81 @@ pub(in crate::agent) fn agent_runtime_action_receipt_safe_detail(
|
||||
}))
|
||||
.ok();
|
||||
}
|
||||
if observation.tool == "preview.validate" {
|
||||
let detail = serde_json::from_str::<serde_json::Value>(
|
||||
observation.detail.as_deref().unwrap_or_default(),
|
||||
)
|
||||
.ok()?;
|
||||
let passed = detail.get("passed")?.as_bool()?;
|
||||
if passed != (observation.status == "ok") {
|
||||
return None;
|
||||
}
|
||||
let revision = detail.get("revision")?.as_u64()?;
|
||||
if revision == 0 {
|
||||
return None;
|
||||
}
|
||||
let report_path = normalize_relative_path(detail.get("reportPath")?.as_str()?).ok()?;
|
||||
let report_parts = report_path.split('/').collect::<Vec<_>>();
|
||||
let revision_text = revision.to_string();
|
||||
if report_parts.len() != 7
|
||||
|| report_parts[..3] != [".agent", "runtime", "browser-validations"]
|
||||
|| report_parts[5] != revision_text.as_str()
|
||||
|| report_parts[6] != "validation.json"
|
||||
{
|
||||
return None;
|
||||
}
|
||||
if receipt_owner.is_some_and(|(agent_id, run_id)| {
|
||||
report_parts[3] != agent_id || report_parts[4] != run_id
|
||||
}) {
|
||||
return None;
|
||||
}
|
||||
let evidence_root = report_parts[..6].join("/");
|
||||
let screenshot_values = detail.get("screenshots")?.as_array()?;
|
||||
if screenshot_values.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
let mut screenshots = screenshot_values
|
||||
.iter()
|
||||
.map(|value| normalize_relative_path(value.as_str()?).ok())
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
screenshots.sort_unstable();
|
||||
if screenshots
|
||||
!= [
|
||||
format!("{evidence_root}/desktop.png"),
|
||||
format!("{evidence_root}/mobile.png"),
|
||||
]
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let playtest = detail.get("playtest");
|
||||
let playtest_passed = match playtest {
|
||||
None | Some(serde_json::Value::Null) => None,
|
||||
Some(value) => Some(value.get("passed")?.as_bool()?),
|
||||
};
|
||||
let playtest_scenario = match playtest {
|
||||
None | Some(serde_json::Value::Null) => None,
|
||||
Some(value) => Some(value.get("scenario")?.as_str()?),
|
||||
};
|
||||
if playtest_scenario
|
||||
.is_some_and(|scenario| !matches!(scenario, "generic-v1" | "lane-defense-v1"))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
return serde_json::to_string(&serde_json::json!({
|
||||
"passed": passed,
|
||||
"revision": revision,
|
||||
"reportPath": report_path,
|
||||
"screenshots": screenshots,
|
||||
"diagnosticsCount": detail
|
||||
.get("diagnostics")
|
||||
.and_then(serde_json::Value::as_array)
|
||||
.map(Vec::len)
|
||||
.unwrap_or(0),
|
||||
"playtestPassed": playtest_passed,
|
||||
"playtestScenario": playtest_scenario,
|
||||
}))
|
||||
.ok();
|
||||
}
|
||||
if observation.tool == GAME_CREATOR_MCP_CALL_TOOL {
|
||||
return game_creator_mcp_public_result_metadata(
|
||||
observation.detail.as_deref().unwrap_or_default(),
|
||||
@@ -1142,13 +1239,18 @@ pub(crate) fn agent_runtime_tool_action_input_summary(
|
||||
)
|
||||
}
|
||||
"canvas.asset_generate" => format!(
|
||||
"promptChars={} · outputPath={} · aspectRatio={} · imageSize={} · assetKind={} · assetLabel={}",
|
||||
"promptChars={} · outputPath={} · aspectRatio={} · imageSize={} · assetKind={} · assetLabel={} · replaceExisting={}",
|
||||
chars(&["prompt"]),
|
||||
text(&["outputPath", "output_path"]),
|
||||
text(&["aspectRatio", "aspect_ratio"]),
|
||||
text(&["imageSize", "image_size"]),
|
||||
text(&["assetKind", "asset_kind"]),
|
||||
text(&["assetLabel", "asset_label"]),
|
||||
input
|
||||
.get("replaceExisting")
|
||||
.or_else(|| input.get("replace_existing"))
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
.unwrap_or(false),
|
||||
),
|
||||
"blackboard.write" => format!(
|
||||
"title={} · contentChars={}",
|
||||
|
||||
+245
-35
@@ -7,6 +7,13 @@ pub(crate) struct AgentRuntimeAutonomousSourcePayloadStats {
|
||||
pub(crate) total_chars: usize,
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn agent_runtime_autonomous_project_verify_available(root: &Path) -> bool {
|
||||
let package_path = root.join("package.json");
|
||||
fs::symlink_metadata(package_path)
|
||||
.map(|metadata| metadata.file_type().is_file() && !metadata.file_type().is_symlink())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn add_agent_runtime_autonomous_source_field(
|
||||
stats: &mut AgentRuntimeAutonomousSourcePayloadStats,
|
||||
value: Option<&serde_json::Value>,
|
||||
@@ -357,13 +364,15 @@ pub(in crate::agent) fn validate_agent_runtime_autonomous_initial_collaboration_
|
||||
}
|
||||
let art_artifacts =
|
||||
autonomous_initial_delegate_expected_artifacts(art_asset_plan, "art-asset-plan")?;
|
||||
if !art_artifacts
|
||||
.iter()
|
||||
.any(|path| path == "assets/art-spritesheet.png")
|
||||
{
|
||||
return Err(autonomous_initial_collaboration_contract_error(
|
||||
"首批 art-asset-plan 的 expectedArtifacts 必须包含 assets/art-spritesheet.png",
|
||||
));
|
||||
let missing_artifacts = ["assets/manifest.art.json", "assets/art-spritesheet.png"]
|
||||
.into_iter()
|
||||
.filter(|required| !art_artifacts.iter().any(|path| path == required))
|
||||
.collect::<Vec<_>>();
|
||||
if !missing_artifacts.is_empty() {
|
||||
return Err(autonomous_initial_collaboration_contract_error(format!(
|
||||
"首批 art-asset-plan 的 expectedArtifacts 缺少:{}",
|
||||
missing_artifacts.join(", ")
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -438,6 +447,23 @@ pub(super) fn validate_agent_runtime_autonomous_plan_liveness_at(
|
||||
plan: &AgentRuntimeToolPlan,
|
||||
supervisor_requires_delegated_repair: bool,
|
||||
) -> Result<(), String> {
|
||||
if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|
||||
&& autonomous_manifest_dag_in_progress_at(root)?
|
||||
{
|
||||
let waits_for_manifest = plan.response.trim().is_empty()
|
||||
&& plan.plan_update.is_none()
|
||||
&& !plan.actions.is_empty()
|
||||
&& plan
|
||||
.actions
|
||||
.iter()
|
||||
.all(|action| matches!(action.tool.trim(), "task.list" | "agent.run_status"));
|
||||
if waits_for_manifest {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(format!(
|
||||
"{AGENT_RUNTIME_AUTONOMOUS_MANIFEST_DAG_WAIT_LIVENESS_ERROR_PREFIX};专业 task 尚未全部完成,本轮只能调用 task.list 与 agent.run_status 读取进度。不得提前验证、试玩、返工、修改项目、委派或返回最终回复"
|
||||
));
|
||||
}
|
||||
if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|
||||
&& agent_runtime_autonomous_supervisor_plan_prepares_repair(plan)
|
||||
{
|
||||
@@ -503,6 +529,34 @@ pub(super) fn validate_agent_runtime_autonomous_plan_liveness_at(
|
||||
)
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn autonomous_manifest_dag_in_progress_at(
|
||||
root: &Path,
|
||||
) -> Result<bool, String> {
|
||||
let manifest = read_manifest_for_project(root)?;
|
||||
let seed_task_ids = new_game_creation_app_seed_tasks()
|
||||
.into_iter()
|
||||
.map(|task| task.id)
|
||||
.collect::<BTreeSet<_>>();
|
||||
let seed_tasks = manifest
|
||||
.tasks
|
||||
.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)
|
||||
}
|
||||
|
||||
pub(crate) fn validate_agent_runtime_autonomous_plan_liveness(
|
||||
agent_id: &str,
|
||||
loop_index: usize,
|
||||
@@ -621,7 +675,10 @@ pub(crate) fn validate_agent_runtime_autonomous_plan_liveness(
|
||||
let verified_revision = verification_gate.verified_revision;
|
||||
if verification_gate.requires_verification
|
||||
&& mutation_revision.is_some()
|
||||
&& verified_revision != mutation_revision
|
||||
&& !mutation_revision.is_some_and(|mutation_revision| {
|
||||
verified_revision
|
||||
.is_some_and(|verified_revision| verified_revision >= mutation_revision)
|
||||
})
|
||||
{
|
||||
if has_mutation
|
||||
|| has_verification
|
||||
@@ -673,44 +730,57 @@ pub(in crate::agent) fn agent_runtime_autonomous_verified_delivery_allows_plan_c
|
||||
verification_gate: &AgentRuntimeVerificationGate,
|
||||
) -> bool {
|
||||
agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|
||||
&& verification_gate.mutation_revision.is_some()
|
||||
&& verification_gate.verified_revision == verification_gate.mutation_revision
|
||||
&& verification_gate
|
||||
.mutation_revision
|
||||
.is_some_and(|mutation_revision| {
|
||||
verification_gate
|
||||
.verified_revision
|
||||
.is_some_and(|verified_revision| verified_revision >= mutation_revision)
|
||||
})
|
||||
&& verification_gate.last_verification_status.as_deref()
|
||||
== Some(AGENT_RUNTIME_VERIFICATION_STATUS_PASSED)
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn validate_agent_runtime_autonomous_read_only_delivery_plan(
|
||||
agent_id: &str,
|
||||
read_only_delivery: bool,
|
||||
plan: &AgentRuntimeToolPlan,
|
||||
) -> Result<(), String> {
|
||||
if !read_only_delivery {
|
||||
return Ok(());
|
||||
}
|
||||
let forbidden = plan.actions.iter().find(|action| {
|
||||
!matches!(
|
||||
action.tool.trim(),
|
||||
"memory.read"
|
||||
| "conversation.read"
|
||||
| "asset.list"
|
||||
| "project.index"
|
||||
| "project.search"
|
||||
| "project.diff"
|
||||
| "git.inspect"
|
||||
| "file.list"
|
||||
| "file.read"
|
||||
| "task.list"
|
||||
| "agent.action_history"
|
||||
| "agent.run_status"
|
||||
| "command.output_read"
|
||||
| "command.poll"
|
||||
| "image.inspect"
|
||||
)
|
||||
let forbidden = plan.actions.iter().find(|action| match action.tool.trim() {
|
||||
"memory.read"
|
||||
| "conversation.read"
|
||||
| "asset.list"
|
||||
| "project.index"
|
||||
| "project.search"
|
||||
| "project.diff"
|
||||
| "git.inspect"
|
||||
| "file.list"
|
||||
| "file.read"
|
||||
| "task.list"
|
||||
| "agent.action_history"
|
||||
| "agent.run_status"
|
||||
| "command.output_read"
|
||||
| "command.poll"
|
||||
| "image.inspect" => false,
|
||||
"preview.validate" => agent_id != "preview-playtest",
|
||||
"command.run_limited" => {
|
||||
agent_id != "preview-readiness"
|
||||
|| action
|
||||
.input
|
||||
.get("commandId")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
!= Some("game.static_smoke")
|
||||
}
|
||||
_ => true,
|
||||
});
|
||||
let Some(forbidden) = forbidden else {
|
||||
return Ok(());
|
||||
};
|
||||
Err(format!(
|
||||
"{AGENT_RUNTIME_AUTONOMOUS_READ_ONLY_MUTATION_ERROR_PREFIX};当前合同只允许读取项目事实并返回审查结论,拒绝动作 {}。不得写文件、推进 revision、启动命令、修改任务/记忆/黑板或委派其它 Agent",
|
||||
"{AGENT_RUNTIME_AUTONOMOUS_READ_ONLY_MUTATION_ERROR_PREFIX};当前合同只允许读取项目事实;preview-readiness 仅额外允许固定 game.static_smoke,preview-playtest 仅额外允许 preview.validate,拒绝动作 {}。不得写文件、推进 revision、启动任意命令、修改任务/记忆/黑板或委派其它 Agent",
|
||||
forbidden.tool.trim()
|
||||
))
|
||||
}
|
||||
@@ -854,6 +924,13 @@ pub(in crate::agent) fn agent_runtime_task_requires_read_only_delivery_at(
|
||||
runtime.delegation_id.as_deref(),
|
||||
) {
|
||||
(None, None, None) => return Ok(fallback()),
|
||||
(Some(parent_agent_id), Some(parent_run_id), None)
|
||||
if runtime.source == "agent-ready-task-scheduler"
|
||||
&& !parent_agent_id.trim().is_empty()
|
||||
&& !parent_run_id.trim().is_empty() =>
|
||||
{
|
||||
return Ok(fallback());
|
||||
}
|
||||
(Some(parent_agent_id), Some(parent_run_id), Some(delegation_id))
|
||||
if !parent_agent_id.trim().is_empty()
|
||||
&& !parent_run_id.trim().is_empty()
|
||||
@@ -1191,10 +1268,40 @@ pub(in crate::agent) fn restrict_agent_runtime_autonomous_delegated_playtest_rep
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn restrict_agent_runtime_autonomous_reverification_repair_tools(
|
||||
pub(in crate::agent) fn restrict_agent_runtime_autonomous_manifest_dag_wait_tools(
|
||||
request: &mut LlmRunRequest,
|
||||
) -> Result<(), String> {
|
||||
let allowed_function_names = ["project.verify", "command.run_limited"]
|
||||
let allowed_function_names = ["task.list", "agent.run_status"]
|
||||
.into_iter()
|
||||
.map(|tool| {
|
||||
native_runtime_function_name(tool)
|
||||
.ok_or_else(|| format!("无法生成 autonomous manifest 等待工具名:{tool}"))
|
||||
})
|
||||
.collect::<Result<BTreeSet<_>, _>>()?;
|
||||
request
|
||||
.function_tools
|
||||
.retain(|tool| allowed_function_names.contains(&tool.name));
|
||||
if request.function_tools.len() != allowed_function_names.len() {
|
||||
return Err("autonomous manifest 等待工具目录不完整".to_string());
|
||||
}
|
||||
request.max_output_tokens = Some(
|
||||
request
|
||||
.max_output_tokens
|
||||
.unwrap_or(AGENT_RUNTIME_AUTONOMOUS_FORCED_ACTION_MAX_OUTPUT_TOKENS)
|
||||
.min(AGENT_RUNTIME_AUTONOMOUS_FORCED_ACTION_MAX_OUTPUT_TOKENS),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn restrict_agent_runtime_autonomous_reverification_repair_tools(
|
||||
request: &mut LlmRunRequest,
|
||||
project_verify_available: bool,
|
||||
) -> Result<(), String> {
|
||||
let mut allowed_tools = vec!["command.run_limited"];
|
||||
if project_verify_available {
|
||||
allowed_tools.push("project.verify");
|
||||
}
|
||||
let allowed_function_names = allowed_tools
|
||||
.into_iter()
|
||||
.map(|tool| {
|
||||
native_runtime_function_name(tool)
|
||||
@@ -1218,6 +1325,7 @@ pub(in crate::agent) fn restrict_agent_runtime_autonomous_reverification_repair_
|
||||
|
||||
pub(in crate::agent) fn restrict_agent_runtime_autonomous_verification_repair_tools(
|
||||
request: &mut LlmRunRequest,
|
||||
project_verify_available: bool,
|
||||
) -> Result<(), String> {
|
||||
let mut allowed_function_names = BTreeSet::new();
|
||||
for tool in [
|
||||
@@ -1227,18 +1335,22 @@ pub(in crate::agent) fn restrict_agent_runtime_autonomous_verification_repair_to
|
||||
"project.patchset",
|
||||
"project.restore",
|
||||
"canvas.asset_generate",
|
||||
"project.verify",
|
||||
"command.run_limited",
|
||||
] {
|
||||
let function_name = native_runtime_function_name(tool)
|
||||
.ok_or_else(|| format!("无法生成 autonomous 验证修复工具名:{tool}"))?;
|
||||
allowed_function_names.insert(function_name);
|
||||
}
|
||||
if project_verify_available {
|
||||
let function_name = native_runtime_function_name("project.verify")
|
||||
.ok_or_else(|| "无法生成 autonomous 验证修复工具名:project.verify".to_string())?;
|
||||
allowed_function_names.insert(function_name);
|
||||
}
|
||||
request
|
||||
.function_tools
|
||||
.retain(|tool| allowed_function_names.contains(&tool.name));
|
||||
apply_agent_runtime_autonomous_source_schema_limits(&mut request.function_tools)?;
|
||||
for required_tool in ["file.patch", "project.verify"] {
|
||||
for required_tool in ["file.patch", "command.run_limited"] {
|
||||
let required = native_runtime_function_name(required_tool)
|
||||
.ok_or_else(|| format!("无法生成 autonomous 验证修复必需工具名:{required_tool}"))?;
|
||||
if !request
|
||||
@@ -1354,7 +1466,64 @@ pub(in crate::agent) fn agent_runtime_protocol_error_requires_supervisor_collabo
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::agent_runtime_task_explicitly_requires_read_only_delivery;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn autonomous_manifest_dag_waits_only_after_seed_execution_starts() {
|
||||
let temporary = tempfile::tempdir().expect("create manifest DAG policy root");
|
||||
let root = temporary.path().join("project");
|
||||
init_local_game_project_at(&root, "manifest-dag-policy", "测试正式任务图等待")
|
||||
.expect("init manifest DAG policy project");
|
||||
assert!(!autonomous_manifest_dag_in_progress_at(&root).expect("read pending DAG"));
|
||||
|
||||
update_manifest_task_status_at(
|
||||
&root,
|
||||
"design-director",
|
||||
GameCreationAppTaskStatus::Running,
|
||||
)
|
||||
.expect("start first manifest task");
|
||||
assert!(autonomous_manifest_dag_in_progress_at(&root).expect("read running DAG"));
|
||||
|
||||
update_manifest_task_status_at(&root, "design-director", GameCreationAppTaskStatus::Failed)
|
||||
.expect("fail first manifest task");
|
||||
assert!(!autonomous_manifest_dag_in_progress_at(&root).expect("read failed DAG"));
|
||||
update_manifest_task_status_at(
|
||||
&root,
|
||||
"design-director",
|
||||
GameCreationAppTaskStatus::Running,
|
||||
)
|
||||
.expect("resume first manifest task fixture");
|
||||
|
||||
for (local_path, kind) in [
|
||||
("assets/ui-prototype.png", "ui-prototype"),
|
||||
("assets/art-spritesheet.png", "art-spritesheet"),
|
||||
] {
|
||||
std::fs::write(root.join(local_path), b"visual fixture").expect("write visual fixture");
|
||||
register_local_asset_at(
|
||||
&root,
|
||||
local_path,
|
||||
kind,
|
||||
"image/png",
|
||||
"canvas",
|
||||
GameCreationAppAssetSource {
|
||||
kind: GameCreationAppAssetSourceKind::Canvas,
|
||||
canvas_project_id: Some("canvas-project-1".to_string()),
|
||||
resource_id: Some(format!("resource-{kind}")),
|
||||
asset_object_id: Some(format!("asset-object-{kind}")),
|
||||
task_id: Some(format!("task-{kind}")),
|
||||
prompt: Some("测试视觉资产".to_string()),
|
||||
model: Some("gpt-image-2".to_string()),
|
||||
},
|
||||
)
|
||||
.expect("register visual fixture");
|
||||
}
|
||||
|
||||
for task in new_game_creation_app_seed_tasks() {
|
||||
update_manifest_task_status_at(&root, &task.id, GameCreationAppTaskStatus::Completed)
|
||||
.expect("complete manifest task");
|
||||
}
|
||||
assert!(!autonomous_manifest_dag_in_progress_at(&root).expect("read completed DAG"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn autonomous_read_only_detection_rejects_negated_labels() {
|
||||
@@ -1384,4 +1553,45 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn autonomous_read_only_validation_actions_are_role_scoped() {
|
||||
let plan_for = |tool: &str, input: serde_json::Value| AgentRuntimeToolPlan {
|
||||
actions: vec![AgentRuntimeToolAction {
|
||||
tool: tool.to_string(),
|
||||
reason: None,
|
||||
input,
|
||||
}],
|
||||
..AgentRuntimeToolPlan::default()
|
||||
};
|
||||
let smoke = plan_for(
|
||||
"command.run_limited",
|
||||
serde_json::json!({"commandId": "game.static_smoke"}),
|
||||
);
|
||||
let preview = plan_for("preview.validate", serde_json::json!({}));
|
||||
|
||||
assert!(validate_agent_runtime_autonomous_read_only_delivery_plan(
|
||||
"preview-readiness",
|
||||
true,
|
||||
&smoke,
|
||||
)
|
||||
.is_ok());
|
||||
assert!(validate_agent_runtime_autonomous_read_only_delivery_plan(
|
||||
"preview-playtest",
|
||||
true,
|
||||
&preview,
|
||||
)
|
||||
.is_ok());
|
||||
for (agent_id, plan) in [
|
||||
("quality-review", &smoke),
|
||||
("quality-review", &preview),
|
||||
("preview-readiness", &preview),
|
||||
("preview-playtest", &smoke),
|
||||
] {
|
||||
assert!(validate_agent_runtime_autonomous_read_only_delivery_plan(
|
||||
agent_id, true, plan,
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,7 +475,12 @@ pub(crate) fn is_agent_runtime_project_mutation_observation(
|
||||
observation.status == "ok"
|
||||
&& matches!(
|
||||
observation.tool.as_str(),
|
||||
"file.write" | "file.patch" | "file.delete" | "project.patchset" | "project.restore"
|
||||
"file.write"
|
||||
| "file.patch"
|
||||
| "file.delete"
|
||||
| "project.patchset"
|
||||
| "project.restore"
|
||||
| "canvas.asset_generate"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -577,6 +582,7 @@ pub(in crate::agent) fn is_agent_runtime_project_verification_observation(
|
||||
observation.tool == "project.verify"
|
||||
|| (observation.tool == "command.exec"
|
||||
&& agent_runtime_command_exec_is_verification_eligible(observation))
|
||||
|| (observation.tool == "canvas.asset_generate" && observation.status == "ok")
|
||||
|| is_agent_runtime_static_smoke_observation(observation)
|
||||
}
|
||||
|
||||
@@ -587,6 +593,8 @@ pub(in crate::agent) fn agent_runtime_project_verification_label(
|
||||
"game.static_smoke"
|
||||
} else if observation.tool == "command.exec" {
|
||||
"command.exec"
|
||||
} else if observation.tool == "canvas.asset_generate" {
|
||||
"canvas.asset_generate"
|
||||
} else {
|
||||
"project.verify"
|
||||
}
|
||||
@@ -867,6 +875,9 @@ pub(in crate::agent) fn visual_asset_completion_blocker_at_locked(
|
||||
agent_id: &str,
|
||||
required_run_id: Option<&str>,
|
||||
) -> Option<AgentRuntimeToolObservation> {
|
||||
if !editor_api_key_is_configured() {
|
||||
return None;
|
||||
}
|
||||
let (expected_path, expected_kind, label) = match agent_id {
|
||||
"design-foundation" => ("assets/ui-prototype.png", "ui-prototype", "策划界面原型图"),
|
||||
"art-asset-plan" => (
|
||||
|
||||
+62
-6
@@ -1,5 +1,7 @@
|
||||
use super::*;
|
||||
|
||||
const AGENT_RUNTIME_COMPLETION_BLOCKER_TOOL_PLAN_PROTOCOL: &str = "通用完成阻断规则:如果最新 observation 的 tool 为 runtime.autonomous_completion 且 status 为 blocked,本轮禁止直接调用 respond_to_user,也禁止在 legacy response 中填写最终回复;必须先读取该 observation.detail 的 nextRequiredAction,并据此调用合适的读取、修复和验证工具。只有完成要求的动作、取得后续可信 observation 且完成门禁不再阻断后,才能给最终回复;不得反复提交 final response,也不得按项目正文硬编码某一种 blocker 的处理方式。";
|
||||
|
||||
pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
@@ -29,11 +31,23 @@ pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request(
|
||||
agent_runtime_tool_policy_snapshot_for_run_at(root, agent_id, run_id, None, None)?;
|
||||
let autonomous_game_build =
|
||||
tool_policy.run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD;
|
||||
let autonomous_project_verify_available =
|
||||
!autonomous_game_build || agent_runtime_autonomous_project_verify_available(root);
|
||||
let mut allowed_tools = tool_policy.allowed_tools.clone();
|
||||
let mut auto_tools = tool_policy.auto_tools.clone();
|
||||
let mut confirm_tools = tool_policy.confirm_tools.clone();
|
||||
let mut denied_tools = tool_policy.denied_tools.clone();
|
||||
if !autonomous_project_verify_available {
|
||||
allowed_tools.retain(|tool| tool != "project.verify");
|
||||
auto_tools.retain(|tool| tool != "project.verify");
|
||||
confirm_tools.retain(|tool| tool != "project.verify");
|
||||
denied_tools.retain(|tool| tool != "project.verify");
|
||||
}
|
||||
let tool_policy_json = serde_json::to_string_pretty(&serde_json::json!({
|
||||
"allowedTools": tool_policy.allowed_tools,
|
||||
"autoTools": tool_policy.auto_tools,
|
||||
"confirmTools": tool_policy.confirm_tools,
|
||||
"deniedTools": tool_policy.denied_tools,
|
||||
"allowedTools": allowed_tools,
|
||||
"autoTools": auto_tools,
|
||||
"confirmTools": confirm_tools,
|
||||
"deniedTools": denied_tools,
|
||||
}))
|
||||
.map_err(|error| format!("序列化 Agent 工具策略失败:{error}"))?;
|
||||
let collaboration_policy_json = if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
|
||||
@@ -97,6 +111,10 @@ pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request(
|
||||
"command.exec|command.output_read|command.run_limited",
|
||||
"command.exec|command.output_read|command.start|command.poll|command.stdin|command.terminate|command.run_limited",
|
||||
)
|
||||
.replace(
|
||||
"\"assetLabel\":\"素材展示名或null\"}",
|
||||
"\"assetLabel\":\"素材展示名或null\",\"replaceExisting\":false};replaceExisting 只能在带 repairOfDelegationId 的唯一返工委派中设为 true,普通生成必须为 false",
|
||||
)
|
||||
.replace(
|
||||
"agent.delegate 使用 {\"agentId\":\"目标 taskId\",\"task\":\"要委派的后台任务\",\"runId\":\"可选 run id\"},用于把任务投递到另一个 Agent 的独立队列",
|
||||
"agent.delegate 使用 {\"agentId\":\"目标 taskId\",\"task\":\"要委派的后台任务\",\"acceptanceCriteria\":[\"可核对的语义验收条件\"],\"expectedArtifacts\":[\"可选的精确项目内文件路径\"],\"repairOfDelegationId\":null,\"runId\":null},用于用持久验收合同把任务投递到另一个 Agent 的独立队列;expectedArtifacts 不接受 glob,返工时 repairOfDelegationId 指向已认领原 delivery 且 runId 必须为 null",
|
||||
@@ -140,9 +158,13 @@ pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request(
|
||||
);
|
||||
let api_kind = parse_game_creator_llm_api_kind(&llm.api_kind)?;
|
||||
let protocol_prompt = if api_kind == platform_llm::LlmApiKind::Anthropic {
|
||||
"当前 Provider 不提供 function tools,请返回上述 schema 的单个完整 JSON object;不要解释、markdown 或代码围栏。"
|
||||
format!(
|
||||
"当前 Provider 不提供 function tools,请返回上述 schema 的单个完整 JSON object;不要解释、markdown 或代码围栏。\n\n{AGENT_RUNTIME_COMPLETION_BLOCKER_TOOL_PLAN_PROTOCOL}"
|
||||
)
|
||||
} else {
|
||||
"必须直接调用当前请求提供的原生函数:需要更新持久计划时调用 update_agent_plan,需要行动时调用对应动作工具,已有观察足够时调用 respond_to_user。只有步骤或状态真实变化时才单独调用 update_agent_plan;当前 in_progress 步骤已具备执行条件时必须在同一响应调用对应动作工具,不能只改计划解释。不要调用未广告的旧 submit_agent_tool_plan,也不要把计划或动作放在普通文本中。"
|
||||
format!(
|
||||
"必须直接调用当前请求提供的原生函数:需要更新持久计划时调用 update_agent_plan,需要行动时调用对应动作工具,已有观察足够时调用 respond_to_user。只有步骤或状态真实变化时才单独调用 update_agent_plan;当前 in_progress 步骤已具备执行条件时必须在同一响应调用对应动作工具,不能只改计划解释。不要调用未广告的旧 submit_agent_tool_plan,也不要把计划或动作放在普通文本中。\n\n{AGENT_RUNTIME_COMPLETION_BLOCKER_TOOL_PLAN_PROTOCOL}"
|
||||
)
|
||||
};
|
||||
let mut system_prompt = game_creator_agent_runtime_tool_plan_system_prompt_for_agent(agent_id);
|
||||
if autonomous_game_build {
|
||||
@@ -172,6 +194,20 @@ pub(in crate::agent) fn build_game_creator_agent_background_tool_plan_request(
|
||||
request = request
|
||||
.with_function_tools(build_agent_runtime_native_function_tools(mcp_catalog)?)
|
||||
.with_tool_choice(platform_llm::LlmToolChoice::Required);
|
||||
if autonomous_game_build && !editor_api_key_is_configured() {
|
||||
let canvas_function = native_runtime_function_name("canvas.asset_generate")
|
||||
.ok_or_else(|| "无法生成画布素材工具函数名".to_string())?;
|
||||
request
|
||||
.function_tools
|
||||
.retain(|tool| tool.name != canvas_function);
|
||||
}
|
||||
if !autonomous_project_verify_available {
|
||||
let project_verify_function = native_runtime_function_name("project.verify")
|
||||
.ok_or_else(|| "无法生成静态项目验证工具函数名".to_string())?;
|
||||
request
|
||||
.function_tools
|
||||
.retain(|tool| tool.name != project_verify_function);
|
||||
}
|
||||
}
|
||||
request = apply_game_creator_llm_web_search(
|
||||
apply_game_creator_llm_reasoning_effort(request, &llm)?,
|
||||
@@ -352,3 +388,23 @@ pub(in crate::agent) fn build_game_creator_background_agent_context(
|
||||
prompt_history.observations,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::AGENT_RUNTIME_COMPLETION_BLOCKER_TOOL_PLAN_PROTOCOL;
|
||||
|
||||
#[test]
|
||||
fn completion_blocker_protocol_requires_tool_repair_before_response() {
|
||||
let protocol = AGENT_RUNTIME_COMPLETION_BLOCKER_TOOL_PLAN_PROTOCOL;
|
||||
|
||||
assert!(protocol.contains("最新 observation"));
|
||||
assert!(protocol.contains("runtime.autonomous_completion"));
|
||||
assert!(protocol.contains("status 为 blocked"));
|
||||
assert!(protocol.contains("禁止直接调用 respond_to_user"));
|
||||
assert!(protocol.contains("禁止在 legacy response 中填写最终回复"));
|
||||
assert!(protocol.contains("observation.detail 的 nextRequiredAction"));
|
||||
assert!(protocol.contains("读取、修复和验证工具"));
|
||||
assert!(protocol.contains("不得反复提交 final response"));
|
||||
assert!(protocol.contains("不得按项目正文硬编码"));
|
||||
}
|
||||
}
|
||||
|
||||
+55
-7
@@ -178,6 +178,9 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
|
||||
false
|
||||
};
|
||||
let allow_runtime_plan_completion = read_only_delivery || verified_delivery;
|
||||
let autonomous_project_verify_available = run_profile
|
||||
!= AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||
|| agent_runtime_autonomous_project_verify_available(root);
|
||||
let mut autonomous_scaffold_repair_active = false;
|
||||
for repair_attempt in 0..=format_repair_attempts {
|
||||
if game_creator_agent_runtime_cancel_requested_for(root, agent_id, run_id) {
|
||||
@@ -308,6 +311,7 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
|
||||
)
|
||||
})?;
|
||||
validate_agent_runtime_autonomous_read_only_delivery_plan(
|
||||
agent_id,
|
||||
read_only_delivery,
|
||||
&parsed.plan,
|
||||
)
|
||||
@@ -652,6 +656,12 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
|
||||
AGENT_RUNTIME_AUTONOMOUS_SUPERVISOR_DELIVERY_CONVERGENCE_LIVENESS_ERROR_PREFIX,
|
||||
)
|
||||
&& !request.function_tools.is_empty();
|
||||
let force_autonomous_manifest_dag_wait = run_profile
|
||||
== AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||
&& protocol_error.starts_with(
|
||||
AGENT_RUNTIME_AUTONOMOUS_MANIFEST_DAG_WAIT_LIVENESS_ERROR_PREFIX,
|
||||
)
|
||||
&& !request.function_tools.is_empty();
|
||||
let force_autonomous_preview_after_static = run_profile
|
||||
== AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||
&& protocol_error.starts_with(
|
||||
@@ -699,6 +709,7 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
|
||||
|| force_autonomous_pending_verification
|
||||
|| force_autonomous_reverify_after_mutation
|
||||
|| force_autonomous_supervisor_delivery_convergence
|
||||
|| force_autonomous_manifest_dag_wait
|
||||
|| force_autonomous_preview_after_static
|
||||
|| force_autonomous_verified_delivery
|
||||
|| force_autonomous_truncated_scaffold
|
||||
@@ -707,6 +718,14 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
|
||||
{
|
||||
request.function_tools =
|
||||
build_agent_runtime_native_function_tools(&mcp_catalog)?;
|
||||
if !autonomous_project_verify_available {
|
||||
let project_verify_function =
|
||||
native_runtime_function_name("project.verify")
|
||||
.ok_or_else(|| "无法生成静态项目验证工具函数名".to_string())?;
|
||||
request
|
||||
.function_tools
|
||||
.retain(|tool| tool.name != project_verify_function);
|
||||
}
|
||||
}
|
||||
if force_supervisor_initial_collaboration {
|
||||
restrict_agent_runtime_supervisor_collaboration_repair_tools(&mut request)?;
|
||||
@@ -714,7 +733,7 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
|
||||
== AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||
{
|
||||
format!(
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n本次修复的原生工具目录只保留首批协作工具。必须在同一响应一次性建立完整首批合同:code-prototype 必须是非只读实现任务且 expectedArtifacts 包含 game/index.html;quality-review 的 task 必须显式声明只读、不得修改项目,且 expectedArtifacts 必须为 [];如当前 policy 的 requiredStaticAgentIds 包含 art-asset-plan,还必须加入非只读美术生成委派且 expectedArtifacts 包含 assets/art-spritesheet.png。所有静态委派都使用 agent.delegate,repairOfDelegationId=null、runId=null;如当前 policy 还要求 isolated,再在同批补齐 agent.spawn_isolated。不得更新计划、读取、搜索、查询状态、修改项目或返回最终回复。不要解释,不要 markdown,不要代码围栏。"
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n本次修复的原生工具目录只保留首批协作工具。必须在同一响应一次性建立完整首批合同:code-prototype 必须是非只读实现任务且 expectedArtifacts 包含 game/index.html;quality-review 的 task 必须显式声明只读、不得修改项目,且 expectedArtifacts 必须为 [];如当前 policy 的 requiredStaticAgentIds 包含 art-asset-plan,还必须加入非只读美术生成委派且 expectedArtifacts 同时包含 assets/manifest.art.json 与 assets/art-spritesheet.png。所有静态委派都使用 agent.delegate,repairOfDelegationId=null、runId=null;如当前 policy 还要求 isolated,再在同批补齐 agent.spawn_isolated。不得更新计划、读取、搜索、查询状态、修改项目或返回最终回复。不要解释,不要 markdown,不要代码围栏。"
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
@@ -731,9 +750,17 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n当前是 autonomous-game-build 的非只读专业任务,本次修复的原生工具目录只保留项目 mutation 工具。必须立即完成本人 run 的实际项目修改;不得 respond_to_user、验证、更新计划、读取、搜索、查询状态或委派。首次源码字段不得超过 {AGENT_RUNTIME_AUTONOMOUS_SCAFFOLD_SOURCE_MAX_CHARS} 字符,完整写入 game/index.html 时必须保持 HTML 与 script 闭合。不要解释,不要 markdown,不要代码围栏。"
|
||||
)));
|
||||
} else if force_autonomous_specialist_verification_only {
|
||||
restrict_agent_runtime_autonomous_reverification_repair_tools(&mut request)?;
|
||||
restrict_agent_runtime_autonomous_reverification_repair_tools(
|
||||
&mut request,
|
||||
autonomous_project_verify_available,
|
||||
)?;
|
||||
let verification_instruction = if autonomous_project_verify_available {
|
||||
"本次修复的原生工具目录只保留 project.verify 与 command.run_limited;必须立即验证本人 run 的最新 mutation revision"
|
||||
} else {
|
||||
"当前项目没有可用的 package.json,本次修复的原生工具目录只保留 command.run_limited;必须立即调用 game.static_smoke 验证本人 run 的最新 mutation revision"
|
||||
};
|
||||
request.messages.push(LlmMessage::user(format!(
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n当前是 autonomous-game-build 的非只读专业任务,且本人 run 已有 mutation。本次修复的原生工具目录只保留 project.verify 与 command.run_limited;必须立即验证本人 run 的最新 mutation revision,通过后才能 respond_to_user。不得继续修改项目、更新计划、读取、搜索、查询状态、委派或解释。不要 markdown,不要代码围栏。"
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n当前是 autonomous-game-build 的非只读专业任务,且本人 run 已有 mutation。{verification_instruction},通过后才能 respond_to_user。不得继续修改项目、更新计划、读取、搜索、查询状态、委派或解释。不要 markdown,不要代码围栏。"
|
||||
)));
|
||||
} else if force_autonomous_response_plan_completion {
|
||||
restrict_agent_runtime_autonomous_response_plan_repair_tools(&mut request)?;
|
||||
@@ -747,6 +774,11 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
|
||||
request.messages.push(LlmMessage::user(format!(
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n当前父 run 已有 ready 未认领回执、尚未 observed 的持久 claim,或 3 个 active delivery;本次修复的原生工具目录只保留 agent.run_status。必须立即以 agentId=null、scope=all、delegationId=null 查询状态并原子认领、观察 readyDelegateReceipts;不得创建第四次 agent.delegate、更新计划、读取、搜索、修改项目、重复验证或 respond_to_user。收敛完成后再依据最新 project revision 重新规划验证或 repair。不要解释,不要 markdown,不要代码围栏。"
|
||||
)));
|
||||
} else if force_autonomous_manifest_dag_wait {
|
||||
restrict_agent_runtime_autonomous_manifest_dag_wait_tools(&mut request)?;
|
||||
request.messages.push(LlmMessage::user(format!(
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n当前正式 manifest DAG 仍有专业 task 在运行。本次修复的原生工具目录只保留 task.list 与 agent.run_status;必须读取任务图和 Runtime 进度并继续等待,不得提前验证、试玩、返工、修改项目、委派或 respond_to_user。不要解释,不要 markdown,不要代码围栏。"
|
||||
)));
|
||||
} else if force_autonomous_preview_after_static {
|
||||
restrict_agent_runtime_autonomous_preview_after_static_repair_tools(
|
||||
&mut request,
|
||||
@@ -767,14 +799,30 @@ pub(in crate::agent) async fn request_game_creator_agent_background_tool_plan_at
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n本次修复的原生工具目录只保留 file.write、file.patch、file.delete 与 project.patchset。必须直接修改 game/index.html,修复最近一次 preview.validate 已证明的交互、状态或可见控件故障;不得更新计划、读取、搜索、重复验证、查询状态、委派或 respond_to_user。源码仍须遵守单字段 {AGENT_RUNTIME_AUTONOMOUS_SOURCE_FIELD_MAX_CHARS} 字符和单轮总计 {AGENT_RUNTIME_AUTONOMOUS_SOURCE_TOTAL_MAX_CHARS} 字符上限,优先使用小范围 file.patch。不要解释,不要 markdown,不要代码围栏。"
|
||||
)));
|
||||
} else if force_autonomous_reverify_after_mutation {
|
||||
restrict_agent_runtime_autonomous_reverification_repair_tools(&mut request)?;
|
||||
restrict_agent_runtime_autonomous_reverification_repair_tools(
|
||||
&mut request,
|
||||
autonomous_project_verify_available,
|
||||
)?;
|
||||
let verification_instruction = if autonomous_project_verify_available {
|
||||
"本次修复的原生工具目录只保留 project.verify 与 command.run_limited;必须立即验证当前 revision"
|
||||
} else {
|
||||
"当前项目没有可用的 package.json,本次修复的原生工具目录只保留 command.run_limited;必须立即调用 game.static_smoke 验证当前 revision"
|
||||
};
|
||||
request.messages.push(LlmMessage::user(format!(
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n最近一次失败验证后已经完成新的项目修改,旧诊断不再代表当前 revision。本次修复的原生工具目录只保留 project.verify 与 command.run_limited;必须立即验证当前 revision,不得继续更新计划、读取、搜索、查询状态、修改项目或 respond_to_user。不要解释,不要 markdown,不要代码围栏。"
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n最近一次失败验证后已经完成新的项目修改,旧诊断不再代表当前 revision。{verification_instruction},不得继续更新计划、读取、搜索、查询状态、修改项目或 respond_to_user。不要解释,不要 markdown,不要代码围栏。"
|
||||
)));
|
||||
} else if force_autonomous_pending_verification {
|
||||
restrict_agent_runtime_autonomous_verification_repair_tools(&mut request)?;
|
||||
restrict_agent_runtime_autonomous_verification_repair_tools(
|
||||
&mut request,
|
||||
autonomous_project_verify_available,
|
||||
)?;
|
||||
let verification_instruction = if autonomous_project_verify_available {
|
||||
"或在项目已经满足要求时立即调用 project.verify / command.run_limited"
|
||||
} else {
|
||||
"或在项目已经满足要求时立即调用 command.run_limited 的 game.static_smoke"
|
||||
};
|
||||
request.messages.push(LlmMessage::user(format!(
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n本次修复的原生工具目录已收窄。必须直接调用实际项目修改工具修复已知问题,或在项目已经满足要求时立即调用 project.verify / command.run_limited 取得当前 revision 的通过凭证。不得只更新计划、读取、搜索、查询状态或 respond_to_user。源码仍须遵守单字段 {AGENT_RUNTIME_AUTONOMOUS_SOURCE_FIELD_MAX_CHARS} 字符和单轮总计 {AGENT_RUNTIME_AUTONOMOUS_SOURCE_TOTAL_MAX_CHARS} 字符上限;优先使用小范围 file.patch。不要解释,不要 markdown,不要代码围栏。"
|
||||
"上一条输出不符合工具计划协议:{protocol_error}\n本次修复的原生工具目录已收窄。必须直接调用实际项目修改工具修复已知问题,{verification_instruction} 取得当前 revision 的通过凭证。不得只更新计划、读取、搜索、查询状态或 respond_to_user。源码仍须遵守单字段 {AGENT_RUNTIME_AUTONOMOUS_SOURCE_FIELD_MAX_CHARS} 字符和单轮总计 {AGENT_RUNTIME_AUTONOMOUS_SOURCE_TOTAL_MAX_CHARS} 字符上限;优先使用小范围 file.patch。不要解释,不要 markdown,不要代码围栏。"
|
||||
)));
|
||||
} else if force_autonomous_verified_delivery {
|
||||
restrict_agent_runtime_autonomous_verified_delivery_repair_tools(&mut request)?;
|
||||
|
||||
+43
@@ -30,6 +30,49 @@ pub(in crate::agent) fn mark_supervisor_delivery_claims_observed_for_pending_act
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn mark_supervisor_delivery_claims_observed_and_schedule_ready_tasks_at(
|
||||
root: &Path,
|
||||
pending: &AgentRuntimePendingToolAction,
|
||||
observation: &AgentRuntimeToolObservation,
|
||||
) -> Result<(), String> {
|
||||
mark_supervisor_delivery_claims_observed_for_pending_action_at(root, pending, observation)?;
|
||||
if pending.agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|
||||
|| pending.action.tool != "agent.run_status"
|
||||
|| observation.status != "ok"
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
if !autonomous_supervisor_run_status_can_schedule_ready_tasks_at(
|
||||
root,
|
||||
&pending.agent_id,
|
||||
&pending.run_id,
|
||||
)? {
|
||||
return Ok(());
|
||||
}
|
||||
schedule_autonomous_game_build_ready_tasks_at(root, &pending.agent_id, &pending.run_id, 3)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn autonomous_supervisor_run_status_can_schedule_ready_tasks_at(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
run_id: &str,
|
||||
) -> Result<bool, String> {
|
||||
let (profile, _) = agent_runtime_run_profile_identity_at(root, agent_id, run_id, None, None)
|
||||
.map_err(|error| format!("读取 Agent Runtime Run Profile 绑定失败:{error}"))?;
|
||||
if profile != AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD {
|
||||
return Ok(false);
|
||||
}
|
||||
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
|
||||
root,
|
||||
"runtime.run_status.schedule_ready",
|
||||
)
|
||||
.map_err(|error| format!("取得 agent.run_status 调度复核锁失败:{error}"))?;
|
||||
let barrier = static_delegate_completion_barrier_at(root, agent_id, run_id)
|
||||
.map_err(|error| format!("读取专业 Agent 静态委派完成屏障失败:{error}"))?;
|
||||
Ok(barrier.is_clear())
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn run_status_prefixed_payload<'a>(
|
||||
detail: Option<&'a str>,
|
||||
prefix: &str,
|
||||
|
||||
+105
@@ -1,5 +1,36 @@
|
||||
use super::*;
|
||||
|
||||
static RUN_STATUS_OBSERVATION_TEST_COUNTER: std::sync::atomic::AtomicU64 =
|
||||
std::sync::atomic::AtomicU64::new(0);
|
||||
|
||||
fn run_status_observation_test_project_path(label: &str) -> std::path::PathBuf {
|
||||
let counter =
|
||||
RUN_STATUS_OBSERVATION_TEST_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
std::env::temp_dir().join(format!(
|
||||
"genarrative-run-status-observation-{label}-{}-{counter}",
|
||||
std::process::id()
|
||||
))
|
||||
}
|
||||
|
||||
fn init_autonomous_run_status_observation_test_project(
|
||||
label: &str,
|
||||
run_id: &str,
|
||||
) -> std::path::PathBuf {
|
||||
let root = run_status_observation_test_project_path(label);
|
||||
init_local_game_project_at(&root, &format!("project-{label}"), label)
|
||||
.expect("init run_status observation test project");
|
||||
bind_game_creator_agent_runtime_run_profile_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
run_id,
|
||||
AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE,
|
||||
Some(AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD),
|
||||
None,
|
||||
)
|
||||
.expect("bind autonomous run_status observation profile");
|
||||
root
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_ready_prefixes_parse_exact_static_and_isolated_ids() {
|
||||
let detail = concat!(
|
||||
@@ -39,3 +70,77 @@ fn ready_prefix_parser_rejects_false_and_duplicate_blocks() {
|
||||
.contains("重复前置区块")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn autonomous_run_status_schedule_propagates_profile_binding_read_error() {
|
||||
let run_id = "run-status-corrupt-profile-binding";
|
||||
let root = init_autonomous_run_status_observation_test_project("corrupt-profile", run_id);
|
||||
std::fs::write(
|
||||
game_creator_agent_runtime_run_profile_binding_path(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
run_id,
|
||||
),
|
||||
b"{not-json",
|
||||
)
|
||||
.expect("corrupt run profile binding");
|
||||
|
||||
let error = autonomous_supervisor_run_status_can_schedule_ready_tasks_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
run_id,
|
||||
)
|
||||
.expect_err("corrupt profile binding must reach pending reconciliation");
|
||||
assert!(error.contains("读取 Agent Runtime Run Profile 绑定失败"));
|
||||
|
||||
std::fs::remove_dir_all(root).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn autonomous_run_status_schedule_propagates_static_barrier_read_error() {
|
||||
let run_id = "run-status-corrupt-static-barrier";
|
||||
let root = init_autonomous_run_status_observation_test_project("corrupt-barrier", run_id);
|
||||
let delivery_dir = root.join(".agent/runtime/delegation-deliveries");
|
||||
std::fs::create_dir_all(&delivery_dir).expect("create static delivery dir");
|
||||
std::fs::write(delivery_dir.join("corrupt-delivery.json"), b"{not-json")
|
||||
.expect("corrupt static delivery");
|
||||
|
||||
let error = autonomous_supervisor_run_status_can_schedule_ready_tasks_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
run_id,
|
||||
)
|
||||
.expect_err("corrupt static barrier must reach pending reconciliation");
|
||||
assert!(error.contains("读取专业 Agent 静态委派完成屏障失败"));
|
||||
|
||||
std::fs::remove_dir_all(root).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn autonomous_run_status_schedule_waits_for_real_static_barrier() {
|
||||
let run_id = "run-status-waiting-static-barrier";
|
||||
let root = init_autonomous_run_status_observation_test_project("waiting-barrier", run_id);
|
||||
let delivery = new_static_delegate_delivery(
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
"run-status-parent-session",
|
||||
run_id,
|
||||
"run-status-delegate-action",
|
||||
"run-status-waiting-delivery",
|
||||
"code-prototype",
|
||||
"run-status-child-session",
|
||||
"run-status-child-run",
|
||||
);
|
||||
create_or_read_static_delegate_delivery_at(&root, &delivery)
|
||||
.expect("create waiting static delivery");
|
||||
|
||||
assert!(
|
||||
!autonomous_supervisor_run_status_can_schedule_ready_tasks_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
run_id,
|
||||
)
|
||||
.expect("real static barrier should not be an error")
|
||||
);
|
||||
|
||||
std::fs::remove_dir_all(root).ok();
|
||||
}
|
||||
|
||||
+3
-1
@@ -136,7 +136,9 @@ pub(crate) fn agent_runtime_tool_policy_snapshot_for_run_at(
|
||||
.denied_tools
|
||||
.push(GAME_CREATOR_USER_INPUT_REQUEST_TOOL.to_string());
|
||||
}
|
||||
if !autonomous_game_build_agent_can_generate_canvas_asset(agent_id) {
|
||||
if !autonomous_game_build_agent_can_generate_canvas_asset(agent_id)
|
||||
|| !editor_api_key_is_configured()
|
||||
{
|
||||
snapshot
|
||||
.auto_tools
|
||||
.retain(|tool| tool != "canvas.asset_generate");
|
||||
|
||||
@@ -5,6 +5,9 @@ pub(super) static GAME_CREATOR_AGENT_RUNTIME_UPDATE_APP_HANDLE: OnceLock<tauri::
|
||||
pub(super) static STATIC_DELEGATE_PARENT_WAKE_SINGLEFLIGHT: OnceLock<
|
||||
std::sync::Mutex<std::collections::BTreeMap<String, bool>>,
|
||||
> = OnceLock::new();
|
||||
pub(super) static AUTONOMOUS_MANIFEST_PARENT_WAKE_SINGLEFLIGHT: OnceLock<
|
||||
std::sync::Mutex<std::collections::BTreeMap<String, bool>>,
|
||||
> = OnceLock::new();
|
||||
pub(super) static PROVIDER_RETRY_WAKE_SINGLEFLIGHT: OnceLock<
|
||||
std::sync::Mutex<std::collections::BTreeSet<String>>,
|
||||
> = OnceLock::new();
|
||||
@@ -100,7 +103,7 @@ pub(crate) const AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE: &str = "project-supervisor
|
||||
pub(super) const AGENT_RUNTIME_RUN_PROFILE_BINDING_SCHEMA_VERSION: &str =
|
||||
"game-creator-run-profile-binding.v1";
|
||||
pub(super) const AGENT_RUNTIME_AUTONOMOUS_COMPLETION_CONTRACT_SCHEMA_VERSION: &str =
|
||||
"game-creator-autonomous-completion-contract.v1";
|
||||
"game-creator-autonomous-completion-contract.v2";
|
||||
pub(super) const AGENT_RUNTIME_AUTONOMOUS_PLAYTEST_RECEIPT_SCHEMA_VERSION: &str =
|
||||
"game-creator-autonomous-playtest-receipt.v1";
|
||||
pub(super) const AGENT_RUNTIME_AUTONOMOUS_GAME_INDEX_MAX_BYTES: u64 = 4 * 1024 * 1024;
|
||||
@@ -157,6 +160,7 @@ pub(super) struct AgentRuntimeAutonomousCompletionContract {
|
||||
pub(super) task_sha256: String,
|
||||
pub(super) baseline_revision: u64,
|
||||
pub(super) baseline_index_sha256: Option<String>,
|
||||
pub(super) baseline_artifacts: Vec<AgentRuntimeAutonomousEvidenceDigest>,
|
||||
pub(super) playtest_scenario: BrowserPlaytestScenario,
|
||||
pub(super) contract_fingerprint: String,
|
||||
pub(super) created_at: u64,
|
||||
@@ -255,6 +259,7 @@ pub(crate) use provider_recovery::{
|
||||
probe_static_delegate_parent_wake_singleflight_coalescing,
|
||||
};
|
||||
pub(crate) use provider_recovery::{
|
||||
schedule_waiting_autonomous_manifest_parent_wake_after_lane_release,
|
||||
schedule_waiting_static_delegate_parent_wake_after_lane_release,
|
||||
static_delegate_parent_wake_error_is_transient,
|
||||
};
|
||||
@@ -274,7 +279,9 @@ pub(crate) use task_queue::{
|
||||
pub(crate) use task_start::start_game_creator_agent_background_task_with_session_lane_hook_at;
|
||||
pub(crate) use task_start::{
|
||||
notify_external_agent_runner_after_background_task_enqueue,
|
||||
schedule_game_creator_agent_ready_tasks_at, start_game_creator_agent_background_task_at,
|
||||
project_autonomous_manifest_ready_task_terminal_at,
|
||||
schedule_autonomous_game_build_ready_tasks_at, schedule_game_creator_agent_ready_tasks_at,
|
||||
start_game_creator_agent_background_task_at,
|
||||
start_game_creator_agent_background_task_for_session_at,
|
||||
start_game_creator_agent_goal_task_in_session_lane_at,
|
||||
start_game_creator_supervisor_background_task_for_session_at,
|
||||
@@ -345,6 +352,8 @@ pub(super) const AGENT_RUNTIME_AUTONOMOUS_FAILED_PLAYTEST_LIVENESS_ERROR_PREFIX:
|
||||
"自主构建 Project Supervisor 的最近一次交互试玩仍未通过";
|
||||
pub(super) const AGENT_RUNTIME_AUTONOMOUS_DELEGATED_PLAYTEST_REPAIR_LIVENESS_ERROR_PREFIX: &str =
|
||||
"自主构建 Project Supervisor 的最近一次交互试玩需要专业 Agent 修复";
|
||||
pub(super) const AGENT_RUNTIME_AUTONOMOUS_MANIFEST_DAG_WAIT_LIVENESS_ERROR_PREFIX: &str =
|
||||
"自主构建 manifest DAG 仍在执行";
|
||||
pub(super) const AGENT_RUNTIME_AUTONOMOUS_RESPONSE_PLAN_LIVENESS_ERROR_PREFIX: &str =
|
||||
"自主构建专业 Agent 已给出结论但结构化计划仍未完成";
|
||||
pub(super) const AGENT_RUNTIME_AUTONOMOUS_TRUNCATED_SCAFFOLD_ERROR_PREFIX: &str =
|
||||
|
||||
@@ -223,6 +223,79 @@ pub(in crate::agent) async fn run_game_creator_agent_background_task_pass_with_c
|
||||
return AgentBackgroundTaskOutcome::WaitingForDelegateReceipts;
|
||||
}
|
||||
}
|
||||
let autonomous_manifest_can_wait = agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|
||||
&& runtime.run_profile == AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD
|
||||
&& !game_creator_agent_runtime_provider_action_batch_exists(
|
||||
&root,
|
||||
&agent_id,
|
||||
&runtime.run_id,
|
||||
)
|
||||
&& supervisor_collaboration_policy_completion_blocker_at_locked(
|
||||
&root,
|
||||
&agent_id,
|
||||
&runtime.run_id,
|
||||
)
|
||||
.is_none()
|
||||
&& isolated_join_completion_blocker_at(&root, &agent_id, &runtime.run_id).is_none()
|
||||
&& static_delegate_completion_blocker_at(&root, &agent_id, &runtime.run_id)
|
||||
.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(
|
||||
&root,
|
||||
&agent_id,
|
||||
&session_id,
|
||||
runtime,
|
||||
&format!("读取自主构建 manifest 等待屏障失败:{error}"),
|
||||
);
|
||||
}
|
||||
};
|
||||
if manifest_in_progress {
|
||||
let blocker =
|
||||
autonomous_game_build_completion_blocker_at_locked(&root, &runtime)
|
||||
.unwrap_or_else(|| AgentRuntimeToolObservation {
|
||||
tool: "runtime.autonomous_completion".to_string(),
|
||||
status: "blocked".to_string(),
|
||||
summary: "自主构建 manifest DAG 尚未完成".to_string(),
|
||||
detail: Some("manifest 子任务仍在运行".to_string()),
|
||||
});
|
||||
if let Err(error) = persist_waiting_autonomous_manifest_parent_context_at(
|
||||
&root,
|
||||
&mut runtime,
|
||||
&task,
|
||||
&plan,
|
||||
&mut observations,
|
||||
loop_index,
|
||||
&mut context_tracker,
|
||||
blocker,
|
||||
) {
|
||||
return fail_game_creator_agent_background_context_at(
|
||||
&root,
|
||||
&agent_id,
|
||||
&session_id,
|
||||
runtime,
|
||||
&format!("持久化 manifest 父 run 等待状态失败:{error}"),
|
||||
);
|
||||
}
|
||||
return AgentBackgroundTaskOutcome::WaitingForManifestTasks;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut resumed_provider_batch = if game_creator_agent_runtime_provider_action_batch_exists(
|
||||
&root,
|
||||
@@ -1026,12 +1099,11 @@ pub(in crate::agent) async fn run_game_creator_agent_background_task_pass_with_c
|
||||
} else if blocker.tool == "runtime.autonomous_completion" {
|
||||
runtime.status = "running".to_string();
|
||||
runtime.phase = "planning".to_string();
|
||||
runtime.current_action = "补齐自主构建可玩交付证据".to_string();
|
||||
runtime.current_action = "补齐自主构建正式项目交付".to_string();
|
||||
runtime.waiting_on =
|
||||
"新的 game/index.html、当前 revision 静态自检与交互试玩回执".to_string();
|
||||
runtime.next_step =
|
||||
"按完成合同继续实现,依次通过 game.static_smoke 与 preview.validate"
|
||||
"manifest 任务图、正式产物、当前 revision 静态自检与交互试玩回执"
|
||||
.to_string();
|
||||
runtime.next_step = "先等待或核对专业 DAG 产物,再对最新 revision 依次通过 game.static_smoke 与 preview.validate".to_string();
|
||||
} else {
|
||||
runtime.current_action = "拒绝在验证未闭环时完成任务".to_string();
|
||||
runtime.waiting_on = "最后一次项目修改后的 project.verify、可验证 command.exec 或 game.static_smoke".to_string();
|
||||
@@ -1982,7 +2054,7 @@ pub(in crate::agent) async fn run_game_creator_agent_background_task_pass_with_c
|
||||
return AgentBackgroundTaskOutcome::NeedsReconciliation;
|
||||
}
|
||||
if let Err(error) =
|
||||
mark_supervisor_delivery_claims_observed_for_pending_action_at(
|
||||
mark_supervisor_delivery_claims_observed_and_schedule_ready_tasks_at(
|
||||
&root,
|
||||
&pending_action,
|
||||
&observation,
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
use super::*;
|
||||
|
||||
fn register_autonomous_recovery_visual_fixture(root: &Path, local_path: &str, kind: &str) {
|
||||
fs::write(root.join(local_path), b"\x89PNG\r\n\x1a\nfixture")
|
||||
.expect("write autonomous recovery visual fixture");
|
||||
register_local_asset_at(
|
||||
root,
|
||||
local_path,
|
||||
kind,
|
||||
"image/png",
|
||||
"canvas",
|
||||
GameCreationAppAssetSource {
|
||||
kind: GameCreationAppAssetSourceKind::Canvas,
|
||||
canvas_project_id: Some("autonomous-recovery-canvas".to_string()),
|
||||
resource_id: Some(format!("resource-{kind}")),
|
||||
asset_object_id: Some(format!("asset-{kind}")),
|
||||
task_id: Some(format!("task-{kind}")),
|
||||
prompt: None,
|
||||
model: None,
|
||||
},
|
||||
)
|
||||
.expect("register autonomous recovery visual fixture");
|
||||
}
|
||||
|
||||
fn prepare_autonomous_completion_evidence(root: &Path, state: &AgentRuntimeState) -> u64 {
|
||||
let contract = read_autonomous_completion_contract(root, &state.agent_id, &state.run_id)
|
||||
.expect("read autonomous completion contract")
|
||||
@@ -23,8 +45,26 @@ fn prepare_autonomous_completion_evidence(root: &Path, state: &AgentRuntimeState
|
||||
"<!doctype html><title>可玩塔防</title><canvas></canvas>",
|
||||
)
|
||||
.expect("write autonomous game index");
|
||||
for (path, content) in [
|
||||
("memory/project.md", "# 项目记忆\n\n正式约束。\n"),
|
||||
("game/game_design.md", "# 游戏设计\n\n核心循环。\n"),
|
||||
("game/balance.json", r#"{"lives":3,"speed":1}"#),
|
||||
(
|
||||
"assets/manifest.art.json",
|
||||
r#"{"assets":[],"status":"not-generated"}"#,
|
||||
),
|
||||
("assets/manifest.audio.json", r#"{"bgm":[],"sfx":[]}"#),
|
||||
("exports/README.md", "# 发布说明\n\n可试玩。\n"),
|
||||
] {
|
||||
write_local_project_file_at(root, path, content)
|
||||
.unwrap_or_else(|error| panic!("write autonomous artifact {path}: {error}"));
|
||||
}
|
||||
revision
|
||||
};
|
||||
for task in new_game_creation_app_seed_tasks() {
|
||||
update_manifest_task_status_at(root, &task.id, GameCreationAppTaskStatus::Completed)
|
||||
.expect("complete autonomous manifest task");
|
||||
}
|
||||
{
|
||||
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
|
||||
root,
|
||||
@@ -215,6 +255,200 @@ fn standard_specialist_empty_plan_has_no_deterministic_final_reply_fallback() {
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn autonomous_manifest_waiting_context_persists_without_finishing_parent_run() {
|
||||
const RUN_ID: &str = "autonomous-manifest-waiting-parent";
|
||||
const TASK: &str = "生成完整小游戏并完成项目任务图";
|
||||
let temporary = tempfile::tempdir().expect("create manifest waiting root");
|
||||
let root = temporary.path().join("project");
|
||||
init_local_game_project_at(&root, "manifest-waiting-project", TASK)
|
||||
.expect("init manifest waiting project");
|
||||
start_game_creator_supervisor_background_task_for_session_at(
|
||||
&root,
|
||||
None,
|
||||
TASK,
|
||||
RUN_ID,
|
||||
AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE,
|
||||
AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD,
|
||||
)
|
||||
.expect("queue autonomous parent task");
|
||||
update_manifest_task_status_at(&root, "design-director", GameCreationAppTaskStatus::Running)
|
||||
.expect("start first manifest task");
|
||||
let task_record = read_latest_game_creator_agent_runtime_task_by_run_id(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
RUN_ID,
|
||||
)
|
||||
.expect("read autonomous parent task")
|
||||
.expect("autonomous parent task exists");
|
||||
let mut runtime = agent_runtime_state_from_task_record(&task_record);
|
||||
let mut observations = Vec::new();
|
||||
let continuation = AgentRuntimeContinuationContext::default();
|
||||
let mut context_tracker = AgentRuntimeContextWindowTracker::from_continuation(&continuation);
|
||||
let blocker = autonomous_game_build_completion_blocker_at_locked(&root, &runtime)
|
||||
.expect("running manifest must block completion");
|
||||
|
||||
persist_waiting_autonomous_manifest_parent_context_at(
|
||||
&root,
|
||||
&mut runtime,
|
||||
TASK,
|
||||
&AgentRuntimeToolPlan::default(),
|
||||
&mut observations,
|
||||
0,
|
||||
&mut context_tracker,
|
||||
blocker,
|
||||
)
|
||||
.expect("persist manifest waiting parent");
|
||||
|
||||
assert_eq!(runtime.status, "running");
|
||||
assert_eq!(runtime.phase, "waiting-for-manifest-tasks");
|
||||
let persisted = read_latest_game_creator_agent_runtime_task_by_run_id(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
RUN_ID,
|
||||
)
|
||||
.expect("read persisted waiting task")
|
||||
.expect("persisted waiting task exists");
|
||||
assert_eq!(persisted.status, "running");
|
||||
assert_eq!(persisted.phase, "waiting-for-manifest-tasks");
|
||||
let bundle = read_game_creator_agent_runtime_context_bundle(&root, &runtime)
|
||||
.expect("read waiting context bundle")
|
||||
.expect("waiting context bundle exists");
|
||||
assert_eq!(bundle.next_loop_index, 0);
|
||||
assert!(bundle.observations.iter().any(|observation| {
|
||||
observation.tool == "runtime.autonomous_completion" && observation.status == "blocked"
|
||||
}));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn missing_completed_visual_asset_fails_same_child_without_retry() {
|
||||
const PARENT_RUN_ID: &str = "autonomous-visual-recovery-parent";
|
||||
const PARENT_TASK: &str = "生成完整小游戏并恢复丢失的正式视觉产物";
|
||||
const CHILD_ID: &str = "design-foundation";
|
||||
let _config_guard = crate::tests::write_test_local_config(
|
||||
r#"{"editorApi":{"apiKey":"visual-recovery-test-key"}}"#.to_string(),
|
||||
);
|
||||
let temporary = tempfile::tempdir().expect("create visual recovery root");
|
||||
let root = temporary.path().join("project");
|
||||
init_local_game_project_at(&root, "visual-recovery-project", PARENT_TASK)
|
||||
.expect("init visual recovery project");
|
||||
let _parent_lane = try_acquire_game_creator_agent_runtime_task_lock(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
)
|
||||
.expect("acquire autonomous parent lane")
|
||||
.expect("autonomous parent lane is free");
|
||||
start_game_creator_supervisor_background_task_for_session_at(
|
||||
&root,
|
||||
None,
|
||||
PARENT_TASK,
|
||||
PARENT_RUN_ID,
|
||||
AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE,
|
||||
AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD,
|
||||
)
|
||||
.expect("queue autonomous visual recovery parent");
|
||||
let _child_lane = try_acquire_game_creator_agent_runtime_task_lock(&root, CHILD_ID)
|
||||
.expect("acquire visual child lane")
|
||||
.expect("visual child lane is free");
|
||||
|
||||
fs::write(root.join("memory/project.md"), "# 项目记忆\n\n恢复测试。\n")
|
||||
.expect("write project memory fixture");
|
||||
fs::write(
|
||||
root.join("game/game_design.md"),
|
||||
"# 游戏设计\n\n恢复丢失图片后才能完成。\n",
|
||||
)
|
||||
.expect("write game design fixture");
|
||||
register_autonomous_recovery_visual_fixture(&root, "assets/ui-prototype.png", "ui-prototype");
|
||||
update_manifest_task_status_at(
|
||||
&root,
|
||||
"design-director",
|
||||
GameCreationAppTaskStatus::Completed,
|
||||
)
|
||||
.expect("complete visual dependency");
|
||||
|
||||
let initial = schedule_autonomous_game_build_ready_tasks_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
PARENT_RUN_ID,
|
||||
1,
|
||||
)
|
||||
.expect("schedule initial visual child");
|
||||
assert_eq!(initial.len(), 1);
|
||||
let initial_run_id = initial[0]
|
||||
.accepted_run_id
|
||||
.clone()
|
||||
.expect("initial visual child run id");
|
||||
assert_eq!(
|
||||
initial_run_id,
|
||||
autonomous_manifest_ready_task_run_id(PARENT_RUN_ID, CHILD_ID)
|
||||
);
|
||||
let initial_record =
|
||||
read_latest_game_creator_agent_runtime_task_by_run_id(&root, CHILD_ID, &initial_run_id)
|
||||
.expect("read initial visual child")
|
||||
.expect("initial visual child exists");
|
||||
let initial_completed = AgentRuntimeTaskRecord {
|
||||
status: "completed".to_string(),
|
||||
phase: "completed".to_string(),
|
||||
current_action: "首轮视觉任务已完成".to_string(),
|
||||
terminal_detail: Some("initial visual completed".to_string()),
|
||||
error: None,
|
||||
updated_at: unix_timestamp(),
|
||||
..initial_record
|
||||
};
|
||||
append_game_creator_agent_runtime_task_record(&root, &initial_completed)
|
||||
.expect("append initial visual terminal");
|
||||
update_manifest_task_status_at(&root, CHILD_ID, GameCreationAppTaskStatus::Completed)
|
||||
.expect("project initial visual completion fixture");
|
||||
|
||||
fs::remove_file(root.join("assets/ui-prototype.png")).expect("remove registered visual file");
|
||||
let downgraded = read_manifest_for_project(&root).expect("refresh missing visual manifest");
|
||||
assert_eq!(
|
||||
downgraded
|
||||
.tasks
|
||||
.iter()
|
||||
.find(|task| task.id == CHILD_ID)
|
||||
.map(|task| &task.status),
|
||||
Some(&GameCreationAppTaskStatus::Pending)
|
||||
);
|
||||
|
||||
let initial_completed_state = agent_runtime_state_from_task_record(&initial_completed);
|
||||
assert!(
|
||||
project_autonomous_manifest_ready_task_terminal_at(&root, &initial_completed_state,)
|
||||
.expect("project missing-visual terminal as failed")
|
||||
);
|
||||
assert_eq!(
|
||||
read_manifest_for_project(&root)
|
||||
.expect("read manifest after missing visual failure")
|
||||
.tasks
|
||||
.iter()
|
||||
.find(|task| task.id == CHILD_ID)
|
||||
.map(|task| &task.status),
|
||||
Some(&GameCreationAppTaskStatus::Failed),
|
||||
"missing visual output must fail the current logical task"
|
||||
);
|
||||
|
||||
assert!(schedule_autonomous_game_build_ready_tasks_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
PARENT_RUN_ID,
|
||||
1,
|
||||
)
|
||||
.expect("repeat scheduling after visual failure")
|
||||
.is_empty());
|
||||
let records = read_all_game_creator_agent_runtime_tasks(&game_creator_agent_runtime_task_path(
|
||||
&root, CHILD_ID,
|
||||
))
|
||||
.expect("read visual child journal");
|
||||
assert_eq!(
|
||||
records
|
||||
.iter()
|
||||
.map(|record| record.run_id.as_str())
|
||||
.collect::<std::collections::BTreeSet<_>>(),
|
||||
std::collections::BTreeSet::from([initial_run_id.as_str()]),
|
||||
"visual failure must not create a second logical child run"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plan_response_precedes_autonomous_supervisor_deterministic_fallback() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -321,7 +321,7 @@ pub(crate) async fn continue_game_creator_agent_pending_tool_action(
|
||||
return;
|
||||
}
|
||||
};
|
||||
if let Err(error) = mark_supervisor_delivery_claims_observed_for_pending_action_at(
|
||||
if let Err(error) = mark_supervisor_delivery_claims_observed_and_schedule_ready_tasks_at(
|
||||
&root,
|
||||
&pending,
|
||||
&observation,
|
||||
|
||||
@@ -809,7 +809,7 @@ pub(crate) fn resume_game_creator_agent_pending_tool_action_at(
|
||||
pending.updated_at = unix_timestamp();
|
||||
write_game_creator_agent_runtime_pending_tool_action(root, &pending)?;
|
||||
if pending.status == AGENT_RUNTIME_PENDING_ACTION_STATUS_OBSERVED_APPROVED {
|
||||
mark_supervisor_delivery_claims_observed_for_pending_action_at(
|
||||
mark_supervisor_delivery_claims_observed_and_schedule_ready_tasks_at(
|
||||
root,
|
||||
&pending,
|
||||
&observation,
|
||||
|
||||
@@ -79,6 +79,64 @@ pub(in crate::agent) fn schedule_waiting_isolated_join_parent_wake_after_lane_re
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn schedule_waiting_autonomous_manifest_parent_wake_after_lane_release(
|
||||
root: PathBuf,
|
||||
agent_id: String,
|
||||
run_id: String,
|
||||
) {
|
||||
let Some(singleflight) =
|
||||
AutonomousManifestParentWakeSingleflight::try_enter(&root, &agent_id, &run_id)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let mut singleflight = singleflight;
|
||||
loop {
|
||||
drive_waiting_autonomous_manifest_parent_wake_pass(&root, &agent_id, &run_id).await;
|
||||
if !singleflight.finish_pass() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn drive_waiting_autonomous_manifest_parent_wake_pass(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
run_id: &str,
|
||||
) {
|
||||
for _ in 0..40 {
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
let Ok(Some(task)) =
|
||||
read_latest_game_creator_agent_runtime_task_by_run_id(root, agent_id, run_id)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if task.status != "running" || task.phase != "waiting-for-manifest-tasks" {
|
||||
return;
|
||||
}
|
||||
match wake_waiting_autonomous_manifest_parent_run_at(root, &task) {
|
||||
Ok(true) => return,
|
||||
Ok(false) => match autonomous_manifest_dag_in_progress_at(root) {
|
||||
Ok(true) => return,
|
||||
Ok(false) => {}
|
||||
Err(error) => {
|
||||
let _ = mark_autonomous_manifest_parent_wake_needs_reconciliation_at(
|
||||
root, agent_id, run_id, &error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
let _ = mark_autonomous_manifest_parent_wake_needs_reconciliation_at(
|
||||
root, agent_id, run_id, &error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn schedule_waiting_static_delegate_parent_wake_after_lane_release(
|
||||
root: PathBuf,
|
||||
agent_id: String,
|
||||
@@ -226,6 +284,62 @@ impl Drop for StaticDelegateParentWakeSingleflight {
|
||||
}
|
||||
}
|
||||
|
||||
struct AutonomousManifestParentWakeSingleflight {
|
||||
key: String,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
impl AutonomousManifestParentWakeSingleflight {
|
||||
fn try_enter(root: &Path, agent_id: &str, run_id: &str) -> Option<Self> {
|
||||
let key = format!("{}\n{agent_id}\n{run_id}", root.to_string_lossy());
|
||||
let registry = AUTONOMOUS_MANIFEST_PARENT_WAKE_SINGLEFLIGHT
|
||||
.get_or_init(|| std::sync::Mutex::new(std::collections::BTreeMap::new()));
|
||||
let mut registry = registry
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
if let Some(rerun_requested) = registry.get_mut(&key) {
|
||||
*rerun_requested = true;
|
||||
None
|
||||
} else {
|
||||
registry.insert(key.clone(), false);
|
||||
Some(Self { key, active: true })
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_pass(&mut self) -> bool {
|
||||
let Some(registry) = AUTONOMOUS_MANIFEST_PARENT_WAKE_SINGLEFLIGHT.get() else {
|
||||
self.active = false;
|
||||
return false;
|
||||
};
|
||||
let mut registry = registry
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
if registry.get(&self.key).copied().unwrap_or(false) {
|
||||
registry.insert(self.key.clone(), false);
|
||||
true
|
||||
} else {
|
||||
registry.remove(&self.key);
|
||||
self.active = false;
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AutonomousManifestParentWakeSingleflight {
|
||||
fn drop(&mut self) {
|
||||
if !self.active {
|
||||
return;
|
||||
}
|
||||
let Some(registry) = AUTONOMOUS_MANIFEST_PARENT_WAKE_SINGLEFLIGHT.get() else {
|
||||
return;
|
||||
};
|
||||
registry
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||
.remove(&self.key);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn probe_static_delegate_parent_wake_singleflight_coalescing(
|
||||
root: &Path,
|
||||
@@ -317,6 +431,58 @@ pub(in crate::agent) fn mark_static_delegate_parent_wake_needs_reconciliation_at
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn mark_autonomous_manifest_parent_wake_needs_reconciliation_at(
|
||||
root: &Path,
|
||||
agent_id: &str,
|
||||
run_id: &str,
|
||||
error: &str,
|
||||
) -> Result<(), String> {
|
||||
let Some(_runtime_lock) =
|
||||
try_acquire_game_creator_agent_runtime_task_lock_with_wait(root, agent_id)?
|
||||
else {
|
||||
return Err(format!(
|
||||
"无法取得父 Agent execution lane 以记录 manifest reconciliation:{agent_id}"
|
||||
));
|
||||
};
|
||||
let mut runtime = read_game_creator_agent_runtime_at(root, agent_id)?.state;
|
||||
if runtime.run_id != run_id || runtime.phase != "waiting-for-manifest-tasks" {
|
||||
return Ok(());
|
||||
}
|
||||
let error = sanitize_agent_runtime_text(error, 500);
|
||||
runtime.status = "failed".to_string();
|
||||
runtime.phase = "needs-reconciliation".to_string();
|
||||
runtime.current_action = "项目任务图唤醒需要人工核对".to_string();
|
||||
runtime.waiting_on = "开发者核对 manifest、子任务终态与父 run 状态".to_string();
|
||||
runtime.next_step = "修复损坏或冲突的任务图状态后显式恢复该 run".to_string();
|
||||
runtime.error = Some(error.clone());
|
||||
runtime.updated_at = unix_timestamp();
|
||||
append_game_creator_agent_runtime_task(root, &runtime)?;
|
||||
refresh_game_creator_agent_runtime_task_queue(root, &mut runtime)?;
|
||||
write_game_creator_agent_runtime_state(root, &runtime)?;
|
||||
let _ = append_game_creator_agent_runtime_event(
|
||||
root,
|
||||
&runtime,
|
||||
"autonomous_manifest.parent_wake.needs_reconciliation",
|
||||
"failed",
|
||||
"needs-reconciliation",
|
||||
"项目任务图已停止自动唤醒,等待开发者核对。",
|
||||
Some(&error),
|
||||
);
|
||||
let _ = append_agent_db_record(
|
||||
root,
|
||||
serde_json::json!({
|
||||
"recordType": "agent.runtime.autonomous_manifest.parent_wake.needs_reconciliation",
|
||||
"agentId": runtime.agent_id,
|
||||
"taskId": runtime.task_id,
|
||||
"sessionId": runtime.session_id,
|
||||
"runId": runtime.run_id,
|
||||
"error": error,
|
||||
}),
|
||||
);
|
||||
emit_game_creator_agent_runtime_update(root, agent_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn persist_waiting_static_delegate_parent_context_at(
|
||||
root: &Path,
|
||||
runtime: &mut AgentRuntimeState,
|
||||
@@ -374,6 +540,63 @@ pub(in crate::agent) fn persist_waiting_static_delegate_parent_context_at(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn persist_waiting_autonomous_manifest_parent_context_at(
|
||||
root: &Path,
|
||||
runtime: &mut AgentRuntimeState,
|
||||
task: &str,
|
||||
plan: &AgentRuntimeToolPlan,
|
||||
observations: &mut Vec<AgentRuntimeToolObservation>,
|
||||
next_loop_index: usize,
|
||||
context_tracker: &mut AgentRuntimeContextWindowTracker,
|
||||
blocker: AgentRuntimeToolObservation,
|
||||
) -> Result<(), String> {
|
||||
let blocker_summary = blocker.summary();
|
||||
runtime.status = "running".to_string();
|
||||
runtime.phase = "waiting-for-manifest-tasks".to_string();
|
||||
runtime.current_action = "等待项目专业任务图收束".to_string();
|
||||
runtime.waiting_on = "manifest 子任务完成、失败或依赖阻塞".to_string();
|
||||
runtime.next_step = "子任务终态后自动调度下一波,任务图到达终态时唤醒当前父 run".to_string();
|
||||
runtime.observations.push(blocker_summary.clone());
|
||||
runtime.updated_at = unix_timestamp();
|
||||
context_tracker.record(&blocker);
|
||||
observations.push(blocker);
|
||||
persist_game_creator_agent_runtime_context(
|
||||
root,
|
||||
runtime,
|
||||
task,
|
||||
plan,
|
||||
observations,
|
||||
next_loop_index,
|
||||
context_tracker,
|
||||
)?;
|
||||
append_game_creator_agent_runtime_task(root, runtime)?;
|
||||
refresh_game_creator_agent_runtime_task_queue(root, runtime)?;
|
||||
write_game_creator_agent_runtime_state(root, runtime)?;
|
||||
let _ = append_game_creator_agent_runtime_event(
|
||||
root,
|
||||
runtime,
|
||||
"observation",
|
||||
"running",
|
||||
"waiting-for-manifest-tasks",
|
||||
&blocker_summary,
|
||||
None,
|
||||
);
|
||||
let _ = append_agent_db_record(
|
||||
root,
|
||||
serde_json::json!({
|
||||
"recordType": "agent.runtime.autonomous_manifest.waiting",
|
||||
"agentId": runtime.agent_id,
|
||||
"taskId": runtime.task_id,
|
||||
"sessionId": runtime.session_id,
|
||||
"runId": runtime.run_id,
|
||||
"status": "waiting-for-manifest-tasks",
|
||||
"nextLoopIndex": next_loop_index,
|
||||
}),
|
||||
);
|
||||
emit_game_creator_agent_runtime_update(root, &runtime.agent_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(in crate::agent) fn persist_waiting_isolated_parent_context_at(
|
||||
root: &Path,
|
||||
runtime: &mut AgentRuntimeState,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user