保留未知 app-server 错误正文
保留非对象和未知形状错误的脱敏内容 补充错误投影回归测试与验收证据
This commit is contained in:
@@ -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::<Vec<_>>();
|
||||
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 [
|
||||
|
||||
@@ -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”,不得退成“无法连接/格式无效/服务器未返回错误信息”。
|
||||
|
||||
@@ -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 断链和真实内存压力仍未在本轮主动制造。
|
||||
|
||||
Reference in New Issue
Block a user