From fd5d11eb0b0a76d9dbbf852f8a32197dad49807c Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:07:30 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=9D=E7=95=99=E6=9C=AA=E7=9F=A5=20app-serv?= =?UTF-8?q?er=20=E9=94=99=E8=AF=AF=E6=AD=A3=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 保留非对象和未知形状错误的脱敏内容 补充错误投影回归测试与验收证据 --- .../src/agent/codex_app_server/mod.rs | 43 +++++++++++++++++++ ...GC命令错误结构化与错误报告口径-2026-10-01.md | 1 + ...€�里程碑】AGC错误具体文本展示-2026-10-04.md | 4 +- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs index db3d8a0a1..41f9d248b 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs @@ -315,6 +315,7 @@ fn game_creator_codex_app_server_error_kind_with_machine_detail( error: &serde_json::Value, ) -> platform_llm::LlmError { let mut fields = Vec::new(); + let mut preserved_detail = false; if let Some(object) = error.as_object() { if let Some(code) = object.get("code").and_then(serde_json::Value::as_str) { if !code.is_empty() @@ -362,6 +363,7 @@ fn game_creator_codex_app_server_error_kind_with_machine_detail( (!safe.trim().is_empty()).then(|| format!("{key}={safe}")) }) .collect::>(); + preserved_detail |= !structured_details.is_empty(); fields.extend(structured_details); } let detail = ["message", "additionalDetails"] @@ -379,6 +381,34 @@ fn game_creator_codex_app_server_error_kind_with_machine_detail( 480, ); fields.push(format!("detail={safe_detail}")); + preserved_detail = true; + } + } else if !error.is_null() { + let serialized = + serde_json::to_string(error).unwrap_or_else(|_| "<无法序列化 error>".into()); + let safe_detail = crate::agent::redact_agent_runtime_error( + std::path::Path::new("__agc_no_project_root__"), + &serialized, + 360, + ); + if !safe_detail.trim().is_empty() { + fields.push(format!("error={safe_detail}")); + preserved_detail = true; + } + } + if !preserved_detail { + // Unknown object shapes and machine-only errors must still carry a bounded + // safe payload. Returning only the native kind recreates the old opaque + // fallback when app-server adds a field this version does not know yet. + let serialized = + serde_json::to_string(error).unwrap_or_else(|_| "<无法序列化 error>".into()); + let safe_detail = crate::agent::redact_agent_runtime_error( + std::path::Path::new("__agc_no_project_root__"), + &serialized, + 360, + ); + if !safe_detail.trim().is_empty() && safe_detail != "{}" { + fields.push(format!("error={safe_detail}")); } } let suffix = if fields.is_empty() { @@ -7161,6 +7191,19 @@ mod tests { assert!(visible.contains("codexErrorInfo=\"other\""), "{visible}"); } + #[test] + fn codex_app_server_failed_turn_keeps_non_object_error_body() { + let failed_turn = serde_json::json!({ + "status": "failed", + "error": "provider returned a structured failure body" + }); + let visible = game_creator_codex_app_server_failed_turn_error(&failed_turn).to_string(); + assert!( + visible.contains("provider returned a structured failure body"), + "{visible}" + ); + } + #[test] fn codex_app_server_failed_turn_maps_stable_categories_and_http_status() { for (info, expected_prefix) in [ diff --git a/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md b/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md index 1f2e79f57..4eaee544e 100644 --- a/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md +++ b/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md @@ -107,6 +107,7 @@ DirectProject 已经解决过同一类问题([`【ADR】DirectProject命令接 - 脱敏只替换敏感值、完整 URL、绝对路径和私钥内容;字段名(例如 `Authorization`、`token`、`api_key`)保留,便于判断错误类别。原始凭据值不进入用户文本、应用日志或项目诊断。 - 上游 app-server 映射保留 `message` / `additionalDetails` 的脱敏摘要;因此 401/403/408/429/413/5xx 等状态不会因为分类为 transport 或 native 而丢失正文。 - app-server 失败对象只有 `codexErrorInfo` 嵌套字段时,也保留其有界 JSON 值(例如 `httpStatusCode`、连接阶段和 `retryable`);不能只留下 `fields=codexErrorInfo`,否则真实 400/401 会再次退化成分类兜底。 +- app-server 的 `error` 若是字符串或未知 JSON 形状,同样保留有界脱敏正文;只有真正为空的错误对象才允许没有更深正文。 - Claude Code(cc)侧车的非零退出、RPC `error`、stdout JSON/UTF-8 解析失败、stderr 和静默超时也走同一映射;侧车 stderr 只在有界收口窗口内读取并进入同一脱敏 detail,不再只写裸 `eprintln!`。 - Codex CLI / Claude sidecar 的 JSONL/JSON 解析失败、失败终态、缺失终态、超时和空回执都保留解析错误、失败事件正文与有界安全片段;`LlmError` 没有 detail 字段的超时/空回执由 Direct `ModelCallFailed.detail` 补回,不再把这些协议事实统一压成单一类别。 - 账户、模型目录、External Editor/资源编辑、发布、素材上传、错误报告与客户端受控工具桥共用同一原则:网络错误保留底层因链,JSON/协议解析保留 serde 原因,HTTP 错误保留状态码与安全正文;只有响应完全没有正文时才说明“未提供 error/code/message”,不得退成“无法连接/格式无效/服务器未返回错误信息”。 diff --git a/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md b/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md index 76f760f71..181745ef3 100644 --- a/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md +++ b/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md @@ -13,7 +13,7 @@ DirectProject 回合失败在确认不是客户端内部不可归类故障时, - `turn.completed.failure` 的前端展示:按 typed 变体显示分类、状态码和脱敏后的 `detail` / `diagnostic`。 - Rust 侧错误文本脱敏:只替换敏感值、URL、绝对路径和私钥内容,保留 HTTP 状态、错误码、字段名和可行动描述。 -- app-server `codexErrorInfo` 只有嵌套机器字段时,保留经有界脱敏的结构化值;`fields=codexErrorInfo` 不能成为唯一正文。 +- app-server `codexErrorInfo` 只有嵌套机器字段时,保留经有界脱敏的结构化值;`error` 是字符串或未知 JSON 形状时也保留正文;`fields=codexErrorInfo` 不能成为唯一正文。 - Transport / Stream / IPC / host process / memory exhaustion 的回归测试与错误事件证据。 ## 不做 @@ -35,7 +35,7 @@ DirectProject 回合失败在确认不是客户端内部不可归类故障时, - `npx vitest run tests/directTurnFailure.test.ts`:15 passed。 - `npx tsc --noEmit -p apps/ai-game-creator-shell/tsconfig.json`:通过。 -- Rust app-server 上游映射定向测试:8 passed;覆盖嵌套 `codexErrorInfo` 与只有机器字段的错误值;应用日志脱敏测试与启动诊断脱敏测试均通过。 +- Rust app-server 上游映射定向测试:9 passed;覆盖嵌套 `codexErrorInfo`、只有机器字段和非对象错误正文;应用日志脱敏测试与启动诊断脱敏测试均通过。 - CC 错误分类回归:Claude sidecar 状态码/超时映射与非支付 409 保留上游状态的定向测试通过;侧车失败统一补充有界 stderr detail。 - 真实 AGC dev smoke:客户端使用 `3080`、后端 `8084`、数据库 `3101`、后台 `3103` 启动;真实 CC 回合复现 `Reached maximum number of turns (8)` 被旧代码错误记为 `transport-closed`,修复后 Tauri 已热重编译重启。修复后的真实 Provider 回放未再次发送,避免无必要的付费请求。 - 真实 AGC 项目诊断发现真实请求 HTTP 400 曾被摘要为 `codex-app-server-error:other detail=fields=codexErrorInfo`,另有 401 仅显示 `codex-app-server-error:unauthorized`;该漏损已在 app-server 投影边界修复。真实 Provider、真实 IPC 断链和真实内存压力仍未在本轮主动制造。