diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs index 12bab6797..f0ff6e7be 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs @@ -50,11 +50,15 @@ pub(crate) fn validate_direct_codex_user_item( if reference.name.trim().is_empty() { return Err("附件缺少文件名".to_string()); } + let status = reference.status.trim(); + if status == "imported" && reference.local_path.trim().is_empty() { + return Err("已导入附件缺少项目路径".to_string()); + } if !reference.local_path.trim().is_empty() { sanitize_attachment_local_path(&reference.local_path) .ok_or_else(|| "附件项目路径无效".to_string())?; } - if !matches!(reference.status.trim(), "imported" | "failed") { + if !matches!(status, "imported" | "failed") { return Err("附件状态无效".to_string()); } } @@ -186,4 +190,28 @@ mod tests { .expect_err("too many inline attachments must be rejected"); assert!(error.contains("最多携带"), "{error}"); } + + #[test] + fn imported_attachment_requires_a_project_path() { + let root = tempfile::tempdir().expect("temp project"); + crate::init_local_game_project_at(root.path(), "validation-test", "校验测试") + .expect("init project"); + let item = serde_json::from_value(json!({ + "type": "message", + "role": "user", + "content": [{ + "type": "agc_attachment_reference", + "name": "attachment.txt", + "mediaType": "text/plain", + "size": 1, + "localPath": "", + "status": "imported" + }], + "id": "turn-1:user" + })) + .expect("deserialize user item"); + let error = validate_direct_codex_user_item(root.path(), &item) + .expect_err("imported attachment without a project path must fail"); + assert!(error.contains("缺少项目路径"), "{error}"); + } }