继续保留 CLI Runner 和本地读取错误
保留 Codex CLI stderr 与 Agent Runner 协议错误 补充本地素材字体和域名解析底层原因 校准 Runner 脱敏回归测试
This commit is contained in:
@@ -149,10 +149,10 @@ fn validate_game_creator_bundled_codex_cli(executable: &Path) -> Result<String,
|
||||
.ok_or_else(|| "内置 Codex CLI 路径无效".to_string())?;
|
||||
let manifest_path = bundle_root.join("manifest.json");
|
||||
let manifest = std::fs::read_to_string(&manifest_path)
|
||||
.map_err(|_| "内置 Codex CLI 缺少完整性清单".to_string())
|
||||
.map_err(|error| format!("内置 Codex CLI 缺少完整性清单:{error}"))
|
||||
.and_then(|value| {
|
||||
serde_json::from_str::<GameCreatorBundledCodexCliManifest>(&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<String,
|
||||
}
|
||||
for relative in layout.files {
|
||||
let path = bundle_root.join(relative);
|
||||
let bytes = std::fs::read(&path).map_err(|_| {
|
||||
let bytes = std::fs::read(&path).map_err(|error| {
|
||||
format!(
|
||||
"内置 Codex CLI 缺少必需组件:{}",
|
||||
"内置 Codex CLI 缺少必需组件:{}:{error}",
|
||||
path.file_name().unwrap_or_default().to_string_lossy()
|
||||
)
|
||||
})?;
|
||||
@@ -195,7 +195,7 @@ pub(in crate::agent) fn game_creator_codex_cli_version_at(
|
||||
return Err(format!("版本检查退出状态为 {}", output.status));
|
||||
}
|
||||
let version = std::str::from_utf8(&output.stdout)
|
||||
.map_err(|_| "版本信息不是 UTF-8".to_string())?
|
||||
.map_err(|error| format!("版本信息不是 UTF-8:{error}"))?
|
||||
.trim();
|
||||
if !version.starts_with("codex-cli ") || version.len() > 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 {
|
||||
|
||||
@@ -1238,7 +1238,7 @@ pub(crate) async fn build_external_asset_download_client(
|
||||
.map(|addresses| addresses.collect::<Vec<_>>())
|
||||
})
|
||||
.await
|
||||
.map_err(|_| "解析画板资产下载域名的任务异常".to_string())?
|
||||
.map_err(|error| format!("解析画板资产下载域名的任务异常:{error}"))?
|
||||
.map_err(|error| format!("解析画板资产下载域名失败:{error}"))?;
|
||||
if addresses.is_empty() {
|
||||
return Err("画板资产下载域名没有可用地址".to_string());
|
||||
|
||||
@@ -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}"));
|
||||
}
|
||||
|
||||
@@ -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::<ExternalAgentRunnerResponse>(&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?<query-redacted>"
|
||||
"agent.runner.failed: request failed <redacted-url><query-redacted>"
|
||||
);
|
||||
assert_eq!(
|
||||
sanitize_agent_runner_output("error password=hunter2", config_dir),
|
||||
|
||||
@@ -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`。
|
||||
|
||||
@@ -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 的回归测试与错误事件证据。
|
||||
|
||||
## 不做
|
||||
|
||||
Reference in New Issue
Block a user