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, }