diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop_tests.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop_tests.rs index 5175b1eca..443c49466 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop_tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop_tests.rs @@ -685,6 +685,53 @@ async fn legacy_game_chat_dynamic_art_retry_runs_are_read_only_and_fail_closed() assert!(!root.join("assets/forged-retry-must-not-write.txt").exists()); } +#[test] +fn non_delegated_art_sources_stay_out_of_game_chat_art_gate() { + for source in ["agent-ready-task-scheduler", "agent-background-task"] { + let temporary = tempfile::tempdir().expect("create non-delegated art root"); + let root = temporary.path().join("project"); + init_local_game_project_at(&root, "non-delegated-art", "非委派美术来源") + .expect("init non-delegated art project"); + let run_id = format!("non-delegated-art-{source}"); + start_game_creator_agent_runtime_task_at( + &root, + "art-director", + "非委派来源不得进入 game-chat 美术谱系门", + &run_id, + source, + "校验工具边界", + vec!["非委派来源不受美术门限制".to_string()], + ) + .expect("start non-delegated art runtime"); + for (tool, input) in [ + ( + "command.output_read", + serde_json::json!({"actionId":"history-probe","startLine":1,"maxLines":5}), + ), + ( + "canvas.asset_generate", + serde_json::json!({"prompt":"恢复视觉规范图","outputPath":AGENT_RUNTIME_ART_SPEC_PATH}), + ), + ( + "file.write", + serde_json::json!({"path":"game/notes.txt","content":"scoped by other policies"}), + ), + ] { + assert!( + game_chat_delegated_art_agent_input_mutation_block( + &root, + "art-director", + &run_id, + tool, + &input, + ) + .is_none(), + "{source}/{tool} 不应被 game-chat 美术门拦截" + ); + } + } +} + #[test] fn game_chat_dynamic_art_retry_guard_preserves_dag_art_and_non_art_delegated_retry() { let dag_temporary = tempfile::tempdir().expect("create full DAG art retry root"); diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/file_ops.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/file_ops.rs index e1469b246..90dcdce36 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/file_ops.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/file_ops.rs @@ -146,6 +146,14 @@ fn game_chat_delegated_art_agent_mutation_scope_at( if game_chat_dynamic_art_retry_claims_game_chat_lineage_at(root, &task)? { return Ok(GameChatDelegatedArtAgentMutationScope::RetryLineageBlocked); } + // Live game-chat dynamic art children only ever run with the + // `agent-delegate` source (retry lineage is classified above). + // Scheduler-seeded DAG tasks and plain background runs must not reach + // the strict predicate: it fails closed on a missing run-profile + // binding and would block even their read-only tools. + if task.source != "agent-delegate" { + return Ok(GameChatDelegatedArtAgentMutationScope::Unrestricted); + } } // `assets/**` is a broad write scope. Grant it only after the same // canonical-output authorization used by Canvas replacement has verified