diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_cli.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_cli.rs index 3dfc50189..7fb2b7dda 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_cli.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_cli.rs @@ -149,10 +149,10 @@ fn validate_game_creator_bundled_codex_cli(executable: &Path) -> Result(&value) - .map_err(|_| "内置 Codex CLI 完整性清单无效".to_string()) + .map_err(|error| format!("内置 Codex CLI 完整性清单无效:{error}")) })?; if manifest.schema_version != codex_bundle::SCHEMA || manifest.platform != layout.platform @@ -167,9 +167,9 @@ fn validate_game_creator_bundled_codex_cli(executable: &Path) -> Result 120 { return Err("返回了无法识别的版本信息".to_string()); @@ -494,7 +494,7 @@ where let count = reader .read(&mut buffer) .await - .map_err(|_| "读取 Codex CLI Agent stderr 失败".to_string())?; + .map_err(|error| format!("读取 Codex CLI Agent stderr 失败:{error}"))?; if count == 0 { break; } @@ -846,14 +846,18 @@ async fn request_game_creator_agent_codex_cli_with_executable( }; let stdout = stdout_task .await - .map_err(|_| { - platform_llm::LlmError::Transport("Codex CLI Agent stdout 读取任务失败".to_string()) + .map_err(|error| { + platform_llm::LlmError::Transport(format!( + "Codex CLI Agent stdout 读取任务失败:{error}" + )) })? .map_err(platform_llm::LlmError::Transport)?; let stderr = stderr_task .await - .map_err(|_| { - platform_llm::LlmError::Transport("Codex CLI Agent stderr 读取任务失败".to_string()) + .map_err(|error| { + platform_llm::LlmError::Transport(format!( + "Codex CLI Agent stderr 读取任务失败:{error}" + )) })? .map_err(platform_llm::LlmError::Transport)?; if timed_out { diff --git a/apps/ai-game-creator-shell/src-tauri/src/assets.rs b/apps/ai-game-creator-shell/src-tauri/src/assets.rs index 5a3724c90..f7881313c 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/assets.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/assets.rs @@ -1238,7 +1238,7 @@ pub(crate) async fn build_external_asset_download_client( .map(|addresses| addresses.collect::>()) }) .await - .map_err(|_| "解析画板资产下载域名的任务异常".to_string())? + .map_err(|error| format!("解析画板资产下载域名的任务异常:{error}"))? .map_err(|error| format!("解析画板资产下载域名失败:{error}"))?; if addresses.is_empty() { return Err("画板资产下载域名没有可用地址".to_string()); diff --git a/apps/ai-game-creator-shell/src-tauri/src/commands.rs b/apps/ai-game-creator-shell/src-tauri/src/commands.rs index 9bcdfd7ee..ea9d974da 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/commands.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/commands.rs @@ -295,7 +295,8 @@ fn validate_local_asset_import_requirements( // leave the early validation path unable to reach the one-shot UAC // repair entry. crate::prepare_game_creator_user_selected_path_for_read(path, false, "本地导入文件")?; - let metadata = fs::symlink_metadata(path).map_err(|_| "读取本地文件失败".to_string())?; + let metadata = + fs::symlink_metadata(path).map_err(|error| format!("读取本地文件失败:{error}"))?; if metadata.file_type().is_symlink() || !metadata.is_file() { return Err("只能导入普通文件".to_string()); } @@ -1031,14 +1032,15 @@ fn read_registered_ui_editor_font( } let target = resolve_local_project_path(root, &asset.local_path)?; prepare_game_creator_private_path_for_read(&target, false, "项目字体")?; - let metadata = fs::symlink_metadata(&target).map_err(|_| "读取项目字体失败".to_string())?; + let metadata = + fs::symlink_metadata(&target).map_err(|error| format!("读取项目字体失败:{error}"))?; if metadata.file_type().is_symlink() || !metadata.is_file() { return Err("项目字体必须是普通文件".to_string()); } if metadata.len() > UI_EDITOR_FONT_MAX_FILE_SIZE { return Err("项目字体超过 8 MiB 限制".to_string()); } - let bytes = fs::read(&target).map_err(|_| "读取项目字体失败".to_string())?; + let bytes = fs::read(&target).map_err(|error| format!("读取项目字体失败:{error}"))?; let source_file_name = target .file_name() .and_then(|value| value.to_str()) @@ -1089,14 +1091,15 @@ pub(crate) fn import_ui_editor_local_fonts( for source in source_paths { let path = Path::new(source.trim()); crate::prepare_game_creator_user_selected_path_for_read(path, false, "本地字体")?; - let metadata = fs::symlink_metadata(path).map_err(|_| "读取本地字体失败".to_string())?; + let metadata = + fs::symlink_metadata(path).map_err(|error| format!("读取本地字体失败:{error}"))?; if metadata.file_type().is_symlink() || !metadata.is_file() { return Err("只能导入普通字体文件".to_string()); } if metadata.len() > UI_EDITOR_FONT_MAX_FILE_SIZE { return Err("字体超过 8 MiB 限制".to_string()); } - let bytes = fs::read(path).map_err(|_| "读取本地字体失败".to_string())?; + let bytes = fs::read(path).map_err(|error| format!("读取本地字体失败:{error}"))?; let source_file_name = path .file_name() .and_then(|value| value.to_str()) @@ -3146,7 +3149,8 @@ pub(crate) fn import_local_project_assets_for_agent_with_write_permit( if metadata.file_type().is_symlink() || !metadata.is_file() { return Err(format!("本地素材只能是普通文件:{normalized}")); } - let bytes = fs::read(&source).map_err(|_| format!("读取本地资源失败:{normalized}"))?; + let bytes = fs::read(&source) + .map_err(|error| format!("读取本地资源失败:{normalized}:{error}"))?; let file_type = agent_local_project_file_type(&normalized, &bytes)?; if metadata.len() > file_type.max_file_size { return Err(format!( @@ -3200,7 +3204,7 @@ pub(crate) fn import_local_project_assets_for_agent_with_write_permit( if target.exists() { prepare_game_creator_private_path_for_read(&target, false, "目标资源")?; let existing_bytes = - fs::read(&target).map_err(|_| "读取目标资源失败".to_string())?; + fs::read(&target).map_err(|error| format!("读取目标资源失败:{error}"))?; if existing_bytes != bytes { return Err(format!("本地资源目标已存在且内容不同:{local_path}")); } diff --git a/apps/ai-game-creator-shell/src-tauri/src/runner/client.rs b/apps/ai-game-creator-shell/src-tauri/src/runner/client.rs index 781a5d4a4..e07f929ce 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/runner/client.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/runner/client.rs @@ -472,8 +472,8 @@ fn send_external_agent_runner_request_with_protocol_and_id_and_timeouts( method: method.to_string(), params, }; - let payload = - serde_json::to_vec(&request).map_err(|_| "序列化 Agent Runner 请求失败".to_string())?; + let payload = serde_json::to_vec(&request) + .map_err(|error| format!("序列化 Agent Runner 请求失败:{error}"))?; let address = SocketAddrV4::new(Ipv4Addr::LOCALHOST, endpoint.port).into(); let mut stream = TcpStream::connect_timeout(&address, connect_timeout) .map_err(|error| format!("连接 Agent Runner 失败:{error}"))?; @@ -489,7 +489,7 @@ fn send_external_agent_runner_request_with_protocol_and_id_and_timeouts( let response_payload = read_external_agent_runner_frame(&mut stream) .map_err(|error| format!("读取 Agent Runner 响应失败:{error}"))?; let response = serde_json::from_slice::(&response_payload) - .map_err(|_| "解析 Agent Runner 响应失败".to_string())?; + .map_err(|error| format!("解析 Agent Runner 响应失败:{error}"))?; if response.protocol_version != protocol_version { return Err("Agent Runner 响应协议版本不兼容".to_string()); } @@ -1708,7 +1708,7 @@ mod diagnostic_log_tests { "agent.runner.failed: request failed https://example.invalid/api?value=1", config_dir, ), - "agent.runner.failed: request failed https://example.invalid/api?" + "agent.runner.failed: request failed " ); assert_eq!( sanitize_agent_runner_output("error password=hunter2", config_dir), diff --git a/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md b/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md index 9e814a1d7..43624394a 100644 --- a/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md +++ b/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md @@ -113,6 +113,7 @@ DirectProject 已经解决过同一类问题([`【ADR】DirectProject命令接 - 账户、模型目录、External Editor/资源编辑、发布、素材上传、错误报告与客户端受控工具桥共用同一原则:网络错误保留底层因链,JSON/协议解析保留 serde 原因,HTTP 错误保留状态码与安全正文;只有响应完全没有正文时才说明“未提供 error/code/message”,不得退成“无法连接/格式无效/服务器未返回错误信息”。 - 素材直传、参考图直传、资源编辑上传/轮询、模型目录初始化和 Direct MCP stdio 的 `send`/读写/序列化失败,不得用 `map_err(|_| 固定句)` 丢掉 `reqwest`、serde 或 OS/IPC 正文;固定句只能作为事实前缀,底层原因必须接在后面。 - 插件 stdout RPC 读取线程不得吞掉 `fill_buf`、UTF-8 或大小上限错误后伪装成“插件进程已退出”;读线程通过挂起 RPC 的错误通道传播真实原因。 +- Codex CLI/Agent Runner 的 stderr、子任务 join、请求序列化/响应解析和本地资源/字体读取错误保留底层 `std::io`、serde 与 UTF-8 原因;路径安全校验仍单独使用稳定的安全拒绝码。 - 浏览器启动/DevTools 握手与 Codex model-catalog 子进程失败保留 stderr、退出状态、解析错误和阶段;稳定机器码仍用于分类,但不能单独成为用户可见正文。 - Node/npm 环境探测和 Web scaffold 构建持续排空并保存有界 stdout/stderr 尾部,失败返回退出状态、超时阶段和脱敏正文;环境预检回执同时下发 `code` 与 `diagnostic`。 - HTTP 409 只有明确包含泥点不足事实时才映射为 `paidCreditsInsufficient`;Claude Code 的普通 409 冲突保留为 `upstreamFailed`。 diff --git a/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md b/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md index b2a498180..ffde9ee82 100644 --- a/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md +++ b/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md @@ -16,6 +16,7 @@ DirectProject 回合失败在确认不是客户端内部不可归类故障时, - app-server `codexErrorInfo` 只有嵌套机器字段时,保留经有界脱敏的结构化值;`error` 是字符串或未知 JSON 形状时也保留正文;`fields=codexErrorInfo` 不能成为唯一正文。 - 素材/参考图/资源编辑上传、资源编辑轮询、模型目录初始化和 Direct MCP stdio 的网络、JSON、写入、读写失败保留底层错误正文;不能由 `map_err(|_| 固定句)` 把它们压成无因的连接或失败提示。 - 插件 stdout RPC 的 `fill_buf`、UTF-8 和响应大小错误通过挂起 RPC 传播,不能在读取线程里吞掉后统一说“插件进程已退出”。 +- Codex CLI/Agent Runner 及本地素材/字体读取保留 stderr、join、序列化/解析、UTF-8 和 OS 错误正文;路径边界拒绝仍保留稳定安全码。 - Transport / Stream / IPC / host process / memory exhaustion 的回归测试与错误事件证据。 ## 不做