From 080ef53b680e49e5a1eee52af9fb2628747f35f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 24 Sep 2026 13:17:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=BF=E4=B8=BB=EF=BC=9A=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=80=81=E5=A4=B1=E6=95=88=E7=9A=84=E4=B8=A4=E6=9D=A1=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E8=B7=AF=E5=BE=84=E7=BB=9F=E4=B8=80=E6=88=90=E5=8F=AF?= =?UTF-8?q?=E9=87=8D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit direct_turn_error.rs:DirectCodexNativeKind::is_retryable 把 Unauthorized 归进 false 组,而同一份事实走 DirectDomainFact::AuthenticationRejected 时是 true,于是 retryable 取决于哪一层先认出它;旧口径对 401 / authentication-required 一律返回 true,这里对齐成可重试,并写明与 recovery_hint 同口径的理由。 direct_runtime/mod.rs:补一条断言(原生 codex-app-server-error:unauthorized 与深层 authentication-required: HTTP 401 同为可重试、都不可反馈给模型);agent:: 过滤 950 passed。 --- .../src-tauri/src/agent/direct_runtime/mod.rs | 21 +++++++++++++++++++ .../src-tauri/src/agent/direct_turn_error.rs | 7 +++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs index 8faae1c00..48ed8236f 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/mod.rs @@ -5674,6 +5674,27 @@ mod tests { assert!(!deep.is_retryable()); } + /// 同一份"登录态失效"事实不许有两套重试口径:原生分类与深层文本都要可重试。 + /// + /// 旧的 `direct_codex_failure_is_retryable` 对 401 / authentication-required 都返回 true; + /// typed 化只把原生分类那一路写成 false,于是 `retryable` 取决于哪一层先认出这条事实, + /// 界面还会出现"请重新登录陶泥儿后重试"却同时标着不可重试的矛盾组合。 + #[test] + fn direct_codex_authentication_failure_is_retryable_in_both_paths() { + let native = DirectTurnError::from_model_call(&LlmError::InvalidRequest( + "codex-app-server-error:unauthorized".into(), + )); + assert!(native.is_retryable()); + let deep = DirectTurnError::turn_failed( + DirectCodexFailureStage::CodeGeneration, + "authentication-required: HTTP 401", + ); + assert!(deep.is_retryable()); + // 可重试不等于该把同一份输入再喂给模型:登录态失效不是模型能修的。 + assert!(!native.is_model_repairable()); + assert!(!deep.is_model_repairable()); + } + #[test] fn direct_project_history_shape_failure_has_explicit_non_retryable_guidance() { let error = DirectTurnError::turn_failed( diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_turn_error.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_turn_error.rs index b8d0ea853..f24b1da7d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_turn_error.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_turn_error.rs @@ -190,9 +190,13 @@ impl DirectCodexNativeKind { /// 这一条分类值不值得当作"再试一次可能修好":与 [`Self::is_terminal`] 互为反义,但语义不同—— /// 这里问的是"用户重试有没有意义",用于失败诊断的 `retryable` 字段。 + /// + /// `Unauthorized` 必须与 [`DirectDomainFact::AuthenticationRejected`] 同口径:登录态失效重登 + /// 之后再发一次是有意义的,`recovery_hint` 也是这么写的。两套分类路径给出相反结论,会让同一 + /// 份事实的 `retryable` 取决于哪一层先认出它。 fn is_retryable(&self) -> bool { match self { - Self::ContextWindowExceeded | Self::RequestTooLarge => true, + Self::ContextWindowExceeded | Self::RequestTooLarge | Self::Unauthorized => true, Self::SessionBudgetExceeded | Self::UsageLimitExceeded | Self::StreamRequired @@ -200,7 +204,6 @@ impl DirectCodexNativeKind { | Self::SandboxError | Self::ThreadRollbackFailed | Self::BadRequest - | Self::Unauthorized | Self::ActiveTurnNotSteerable | Self::Other { .. } => false, }