重命名 Direct 回合失败载荷并让 typed 失败原样下发

- TurnFailed 更名为 SuperErrorFromStringPlusStage:只有 stage 是 typed,detail 仍是产生层字符串
- TurnFailedUnclassified 更名为 Unclassified:深层字符串兜底,连交付阶段都没有
- 拆掉 direct_runtime 与 thread_manager 两处收口重包,typed 失败原样投影成失败载荷
- 审计写盘保留,只是不再把 direct-codex-failure:v2 收口文案当失败载荷的 detail
- 生成绑定重导出(旧 TurnFailed/TurnFailedUnclassified 生成文件删除),前端分支与 directThreadChat 用例同步改名
- 命名债注释补记:这两个载荷臂与 SuperErrorEnumFromStringTyped 属同一笔待清债
This commit is contained in:
2026-10-02 22:11:41 +08:00
parent c18c73fa2f
commit 29eed573a0
14 changed files with 110 additions and 90 deletions
@@ -30,6 +30,10 @@
//! typed 出口。修法在**上游**:让这些产生层直接产出 typed 变体 / 结构化子错误,判定层改成
//! `match`;等上游补完,这张表和 `runtime_error::classify_direct_codex_error` 一起删掉。
//! 补齐之前:**不许往表里加词**——新增分类必须先在产生层加 typed 变体。
//!
//! 同一笔债还挂在两个载荷臂上:[`SuperErrorFromStringPlusStage`](有 stage、没有 typed detail)
//! 与 [`Unclassified`](连 stage 都没有)。名字故意起得又长又扎眼,就是在提醒它们不是设计:
//! `detail` typed 化之后,这两个名字都该消失。
use std::path::Path;
@@ -488,11 +492,15 @@ pub(crate) enum TurnError {
/// —— **控制流,不是失败**:与 [`Self::ReviewRequired`] 同一族,只是产生点在收尾阶段。
/// 调用方把它写回提示词继续跑:不写终态、不进失败载荷、不上报。
RepairRequired { detail: String },
/// 已经写成诊断记录的回合失败。
TurnFailed(TurnFailed),
/// 桥:深层只拿得到字符串的错误。只允许出现在"这一轮已经开始"的层里,
/// 且新分类必须先加 typed 变体,别借这个变体蒙混过关。
TurnFailedUnclassified(TurnFailedUnclassified),
/// 交付阶段失败:`stage` 是 typed 的,`detail` 还是产生层交上来的字符串。
///
/// 名字故意起得又长又扎眼(`Super…FromStringPlusStage`):这是**待清的债**,
/// `detail` typed 化之后这一臂该改名或并入 typed 变体,别把它改漂亮。
SuperErrorFromStringPlusStage(SuperErrorFromStringPlusStage),
/// 桥:深层只拿得到字符串的错误,**连阶段都没有**。只允许出现在"这一轮已经开始"的层里,
/// 且新分类必须先加 typed 变体,别借这个变体蒙混过关。与
/// [`Self::SuperErrorFromStringPlusStage`] 是同一笔债的两半。
Unclassified(Unclassified),
}
/// 项目目录锚不定的原始原因。
@@ -536,20 +544,23 @@ pub(crate) struct TurnInterrupted {
pub(crate) detail: String,
}
/// 已写成诊断记录的回合失败:阶段 + 诊断正文。
/// 已知交付阶段的失败:`stage` 是 typed enum,`detail` 是产生层原文(字符串债)。
///
/// 名字故意起得又长又扎眼(`Super…FromStringPlusStage`):与 [`SuperErrorEnumFromStringTyped`]
/// 同一笔债——`detail` 一天没 typed 化,它一天不配有个正常名字。
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/view/project-development/chat/generated/"))]
pub(crate) struct TurnFailed {
pub(crate) struct SuperErrorFromStringPlusStage {
pub(crate) stage: FailureStage,
pub(crate) detail: String,
}
/// 深层尚未 typed 的字符串错误。
/// 深层尚未 typed 的字符串错误:不知道发生在哪一段,只有一条宿主原文。
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/view/project-development/chat/generated/"))]
pub(crate) struct TurnFailedUnclassified {
pub(crate) struct Unclassified {
pub(crate) detail: String,
}
@@ -589,15 +600,15 @@ impl TurnError {
Self::TurnInterrupted(payload) => Some(TurnFailure::TurnInterrupted(TurnInterrupted {
detail: redact(&payload.detail),
})),
Self::TurnFailed(payload) => Some(TurnFailure::TurnFailed(TurnFailed {
stage: payload.stage,
Self::SuperErrorFromStringPlusStage(payload) => Some(
TurnFailure::SuperErrorFromStringPlusStage(SuperErrorFromStringPlusStage {
stage: payload.stage,
detail: redact(&payload.detail),
}),
),
Self::Unclassified(payload) => Some(TurnFailure::Unclassified(Unclassified {
detail: redact(&payload.detail),
})),
Self::TurnFailedUnclassified(payload) => Some(TurnFailure::TurnFailedUnclassified(
TurnFailedUnclassified {
detail: redact(&payload.detail),
},
)),
// 控制流:这一轮还没结束,不是失败。
Self::ReviewRequired { .. } | Self::RepairRequired { .. } => None,
}
@@ -622,15 +633,15 @@ impl TurnError {
Self::TimedOut(payload) => payload.deadline.message().to_string(),
Self::TurnInterrupted(payload) => payload.detail.clone(),
Self::ReviewRequired { detail } | Self::RepairRequired { detail } => detail.clone(),
Self::TurnFailed(payload) => payload.detail.clone(),
Self::TurnFailedUnclassified(payload) => payload.detail.clone(),
Self::SuperErrorFromStringPlusStage(payload) => payload.detail.clone(),
Self::Unclassified(payload) => payload.detail.clone(),
}
}
/// 已记录失败的诊断阶段;拿不到阶段的错误归到回合主体的代码生成段。
pub(crate) fn turn_failure_stage(&self) -> FailureStage {
match self {
Self::TurnFailed(payload) => payload.stage,
Self::SuperErrorFromStringPlusStage(payload) => payload.stage,
_ => FailureStage::CodeGeneration,
}
}
@@ -642,10 +653,10 @@ impl TurnError {
// 阶段失败 / 桥变体:**认出是哪一类就拦**(产生层还没 typed 出口的深层事实才继续
// 反馈)。已归类的都是模型改不动的事实——凭据 / 权限 / 额度 / 历史一致性与契约变化,
// 把同一份输入再跑一轮只会拿到同一结论;旧的字面量判据也是这个口径。
Self::TurnFailed(payload) => {
Self::SuperErrorFromStringPlusStage(payload) => {
SuperErrorEnumFromStringTyped::classify(&payload.detail).is_none()
}
Self::TurnFailedUnclassified(payload) => {
Self::Unclassified(payload) => {
SuperErrorEnumFromStringTyped::classify(&payload.detail).is_none()
}
_ => false,
@@ -659,12 +670,12 @@ impl TurnError {
Self::TransportClosed(_) => false,
// 超时/中断后重试是常规动作:宿主已经把这一轮收干净了。
Self::TimedOut(_) | Self::TurnInterrupted(_) => true,
Self::TurnFailed(payload) => SuperErrorEnumFromStringTyped::classify(&payload.detail)
.is_none_or(SuperErrorEnumFromStringTyped::is_retryable),
Self::TurnFailedUnclassified(payload) => {
Self::SuperErrorFromStringPlusStage(payload) => {
SuperErrorEnumFromStringTyped::classify(&payload.detail)
.is_none_or(SuperErrorEnumFromStringTyped::is_retryable)
}
Self::Unclassified(payload) => SuperErrorEnumFromStringTyped::classify(&payload.detail)
.is_none_or(SuperErrorEnumFromStringTyped::is_retryable),
_ => false,
}
}
@@ -673,12 +684,12 @@ impl TurnError {
pub(crate) fn public_summary(&self) -> Option<&'static str> {
match self {
Self::ModelCallFailed(payload) => payload.kind.public_summary(),
Self::TurnFailed(payload) => SuperErrorEnumFromStringTyped::classify(&payload.detail)
.and_then(SuperErrorEnumFromStringTyped::public_summary),
Self::TurnFailedUnclassified(payload) => {
Self::SuperErrorFromStringPlusStage(payload) => {
SuperErrorEnumFromStringTyped::classify(&payload.detail)
.and_then(SuperErrorEnumFromStringTyped::public_summary)
}
Self::Unclassified(payload) => SuperErrorEnumFromStringTyped::classify(&payload.detail)
.and_then(SuperErrorEnumFromStringTyped::public_summary),
_ => None,
}
}
@@ -696,9 +707,11 @@ impl TurnError {
Self::TurnInterrupted(_) => {
Some("本轮执行被上游中断,请重试;如持续失败请检查项目诊断")
}
Self::TurnFailed(payload) => stage_recovery_hint(payload.stage, &payload.detail),
Self::SuperErrorFromStringPlusStage(payload) => {
stage_recovery_hint(payload.stage, &payload.detail)
}
// 桥变体没有阶段可依,按回合主体的默认段给建议。
Self::TurnFailedUnclassified(payload) => {
Self::Unclassified(payload) => {
stage_recovery_hint(FailureStage::CodeGeneration, &payload.detail)
}
_ => None,
@@ -743,7 +756,7 @@ impl TurnError {
/// 阶段失败:把深层错误挂到交付的某一段上。深层还没 typed 的口子由这里进桥变体。
pub(crate) fn turn_failed(stage: FailureStage, detail: impl Into<String>) -> Self {
Self::TurnFailed(TurnFailed {
Self::SuperErrorFromStringPlusStage(SuperErrorFromStringPlusStage {
stage,
detail: detail.into(),
})
@@ -757,18 +770,18 @@ impl TurnError {
}
}
/// 桥:深层尚未 typed 的字符串错误落进 [`TurnError::TurnFailedUnclassified`]。
/// 桥:深层尚未 typed 的字符串错误落进 [`TurnError::Unclassified`]。
///
/// 只给"这一轮已经开始"的层用。调用级(权限、校验、并发)必须显式构造对应变体。
impl From<String> for TurnError {
fn from(detail: String) -> Self {
Self::TurnFailedUnclassified(TurnFailedUnclassified { detail })
Self::Unclassified(Unclassified { detail })
}
}
impl From<&str> for TurnError {
fn from(detail: &str) -> Self {
Self::TurnFailedUnclassified(TurnFailedUnclassified {
Self::Unclassified(Unclassified {
detail: detail.to_string(),
})
}
@@ -1134,14 +1147,14 @@ mod tests {
payload
);
let payload = TurnFailure::TurnFailed(TurnFailed {
let payload = TurnFailure::SuperErrorFromStringPlusStage(SuperErrorFromStringPlusStage {
stage: FailureStage::BrowserValidation,
detail: "试玩失败".into(),
});
assert_eq!(
serde_json::to_value(&payload).expect("serialize"),
serde_json::json!({
"type": "turnFailed",
"type": "superErrorFromStringPlusStage",
"stage": "browser-validation",
"detail": "试玩失败"
})
@@ -1954,6 +1954,8 @@ const DIRECT_CODEX_DEFAULT_RECOVERY_HINT: &str = "请重试;如持续失败请
///
/// 返回值是 `direct-codex-failure:v2 ...` 这行宿主收口文本:它只进 `.agent/runtime/errors`
/// 与应用日志、错误上报池,**不再下发给前端**(前端按 typed 变体自己拼用户可见文案)。
/// 调用方现在一律丢弃这个返回值:失败载荷原样透传 typed 变体,收口文案不再当 `detail`,
/// 返回值只剩诊断测试在断言。
pub(crate) fn record_direct_codex_failure(
root: &Path,
failure: &TurnError,
@@ -2020,8 +2022,10 @@ fn direct_codex_error_code(failure: &TurnError) -> &'static str {
| TurnError::RepairRequired { .. } => {
classify_direct_codex_error(&failure.diagnostic_detail())
}
TurnError::TurnFailed(payload) => classify_direct_codex_error(&payload.detail),
TurnError::TurnFailedUnclassified(payload) => classify_direct_codex_error(&payload.detail),
TurnError::SuperErrorFromStringPlusStage(payload) => {
classify_direct_codex_error(&payload.detail)
}
TurnError::Unclassified(payload) => classify_direct_codex_error(&payload.detail),
}
}
@@ -4406,8 +4410,10 @@ pub(crate) async fn run_direct_game_creator_turn_at_with_creation_type_and_emitt
// 在入队前就查过,能到这条通道的只有放行之后的事(连接、配置、历史注入、`turn/start`
// 被拒、模型与交付)。所以不再有"直通调用方"的分支。
Err(failure) => {
let stage = failure.turn_failure_stage();
let error = record_direct_codex_failure(
// 审计照写(`.agent/runtime/errors` / 应用日志 / 错误池),但返回值不再当载荷:
// typed 失败**原样透传**,前端拿到的是真正的变体(模型调用 / 超时 / 通道断开…),
// 而不是被统一重包成 `SuperErrorFromStringPlusStage` 后的 catch-all。
let _ = record_direct_codex_failure(
root,
&failure,
turn_emitter.map(|emitter| emitter.turn_id()),
@@ -4418,10 +4424,7 @@ pub(crate) async fn run_direct_game_creator_turn_at_with_creation_type_and_emitt
if let Some(emitter) = turn_emitter {
emitter.emit("failed", Some("none"));
}
Err(TurnError::TurnFailed(TurnFailed {
stage,
detail: error,
}))
Err(failure)
}
}
}
@@ -158,8 +158,8 @@ mod tests {
TurnFailure::TransportClosed(payload) => payload.diagnostic.clone(),
TurnFailure::TimedOut(payload) => format!("{:?}", payload.deadline),
TurnFailure::TurnInterrupted(payload) => payload.detail.clone(),
TurnFailure::TurnFailed(payload) => payload.detail.clone(),
TurnFailure::TurnFailedUnclassified(payload) => payload.detail.clone(),
TurnFailure::SuperErrorFromStringPlusStage(payload) => payload.detail.clone(),
TurnFailure::Unclassified(payload) => payload.detail.clone(),
TurnFailure::HostDropped => String::new(),
}
}
@@ -22,7 +22,7 @@ use crate::agent::{
direct_codex_user_item_to_prompt, now_ms, record_direct_codex_failure,
redact_agent_runtime_error, run_direct_game_creator_turn_at_with_creation_type_and_emitter,
thread_id_for_project, DirectGameCreatorTurnUpdateEmitter, DispatchedTurn, FailureStage,
TurnCompletion, TurnError, TurnFailed,
TurnCompletion, TurnError,
};
/// 一次放行的占用。持有它就代表这一轮还没收口。
@@ -263,9 +263,10 @@ async fn run_dispatched_direct_turn(
direct_turn_panic_detail(payload.as_ref())
),
);
let stage = failure.turn_failure_stage();
let detail = record_direct_codex_failure(&root, &failure, Some(turn_id.as_str()));
Err(TurnError::TurnFailed(TurnFailed { stage, detail }))
// 审计照写,载荷原样透传:panic 本身已经是一条 `SuperErrorFromStringPlusStage`,
// 不需要再包一层。
let _ = record_direct_codex_failure(&root, &failure, Some(turn_id.as_str()));
Err(failure)
}
};
match outcome {
@@ -16,7 +16,7 @@
use std::path::Path;
use crate::agent::{ThreadEvent, TurnError, TurnFailedUnclassified};
use crate::agent::{ThreadEvent, TurnError, Unclassified};
use super::wire::TurnFailure;
@@ -85,7 +85,7 @@ pub(crate) fn turn_terminal(
// 账本读不出来时(`session_status == "failed"`)没有 typed 原因可用:报告文本就是这一轮
// 唯一的收口依据,按未分类失败发出去,不能让界面停在"已结束、没原因"。
(None, Ok(report)) if session_status == "failed" => {
Some(TurnError::TurnFailedUnclassified(TurnFailedUnclassified {
Some(TurnError::Unclassified(Unclassified {
detail: report.to_string(),
}))
}
@@ -100,7 +100,7 @@ pub(crate) fn turn_terminal(
// 收尾阶段的 `status` 认不出来(当前不可能发生):宁可报一条说不出原因的失败,也不冒充
// 正常收场;载荷照样从 typed 错误投影,保持"只在一处拼载荷"。
(None, None) => TurnCompletion::Failed(
TurnError::TurnFailedUnclassified(TurnFailedUnclassified {
TurnError::Unclassified(Unclassified {
detail: format!("收尾阶段给出的回合终态无法识别:{session_status}"),
})
.terminal_failure(history_root)
@@ -142,8 +142,7 @@ impl SessionOutcome {
mod tests {
use super::*;
use crate::agent::{
ModelCallKind, ThreadEvent, TransportClosed, TurnError, TurnFailedUnclassified,
TurnInterrupted,
ModelCallKind, ThreadEvent, TransportClosed, TurnError, TurnInterrupted, Unclassified,
};
use platform_llm::LlmError;
@@ -180,7 +179,7 @@ mod tests {
fn unknown_session_status_fails_closed() {
let completion = turn_terminal("something-new", Ok("报告"), None, &history_root());
match completion {
TurnCompletion::Failed(TurnFailure::TurnFailedUnclassified(payload)) => {
TurnCompletion::Failed(TurnFailure::Unclassified(payload)) => {
assert!(payload.detail.contains("something-new"));
}
other => panic!("unknown status must fail closed, got {other:?}"),
@@ -208,11 +207,9 @@ mod tests {
fn unreadable_session_ledger_still_reports_a_payload() {
assert_eq!(
turn_terminal("failed", Ok("报告"), None, &history_root()),
TurnCompletion::Failed(TurnFailure::TurnFailedUnclassified(
TurnFailedUnclassified {
detail: "报告".into()
}
))
TurnCompletion::Failed(TurnFailure::Unclassified(Unclassified {
detail: "报告".into()
}))
);
}
@@ -13,8 +13,8 @@ use serde::{Deserialize, Serialize};
use ts_rs::TS;
use crate::agent::{
EnvironmentNotReady, HostStateUnavailable, ModelCallFailed, ProjectRootUnanchored, TimedOut,
TransportClosed, TurnFailed, TurnFailedUnclassified, TurnInterrupted,
EnvironmentNotReady, HostStateUnavailable, ModelCallFailed, ProjectRootUnanchored,
SuperErrorFromStringPlusStage, TimedOut, TransportClosed, TurnInterrupted, Unclassified,
};
/// 失败终态的可下发载荷(`turn.completed.status == "failed"` 时必有,其余终态没有)。
@@ -41,8 +41,8 @@ pub(crate) enum TurnFailure {
TransportClosed(TransportClosed),
TimedOut(TimedOut),
TurnInterrupted(TurnInterrupted),
TurnFailed(TurnFailed),
TurnFailedUnclassified(TurnFailedUnclassified),
SuperErrorFromStringPlusStage(SuperErrorFromStringPlusStage),
Unclassified(Unclassified),
/// 宿主任务提前结束(panic / 被取消):说不出原因的那一种兜底。
HostDropped,
}
@@ -1248,7 +1248,7 @@ export function projectRuntimeVisibleError(
normalized.includes('落盘失败') ||
normalized.includes('持久化失败') ||
normalized.includes('写入失败') ||
// 宿主"历史落盘"的事实句不带"失败"两个字:`TurnFailed` 的原因就是
// 宿主"历史落盘"的事实句不带"失败"两个字:`SuperErrorFromStringPlusStage` 的原因就是
// "DirectProject 收尾历史失败:未确认历史完整落盘"这一类。
normalized.includes('收尾历史失败') ||
normalized.includes('未确认历史完整落盘') ||
@@ -172,9 +172,9 @@ export function directTurnFailureNoticeText(failure: TurnFailure): string {
return `${DIRECT_TURN_SUBJECT} ${directTurnDeadlineText(failure.deadline)}`;
case 'turnInterrupted':
return `${DIRECT_TURN_SUBJECT} 本轮执行被中断,请重试`;
case 'turnFailed':
case 'superErrorFromStringPlusStage':
return `${DIRECT_TURN_SUBJECT}:${DIRECT_FAILURE_STAGE_LABELS[failure.stage]},请检查项目诊断后重试`;
case 'turnFailedUnclassified':
case 'unclassified':
return `${DIRECT_TURN_SUBJECT} 执行失败,请稍后重试`;
case 'hostDropped':
return '陶泥儿回合的宿主任务提前结束(崩溃或任务被取消),本轮已按失败收口,请重试。';
@@ -0,0 +1,13 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { FailureStage } from './FailureStage';
/**
* 已知交付阶段的失败:`stage` 是 typed enum,`detail` 是产生层原文(字符串债)。
*
* 名字故意起得又长又扎眼(`Super…FromStringPlusStage`):与 [`SuperErrorEnumFromStringTyped`]
* 同一笔债——`detail` 一天没 typed 化,它一天不配有个正常名字。
*/
export type SuperErrorFromStringPlusStage = {
stage: FailureStage;
detail: string;
};
@@ -1,7 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { FailureStage } from './FailureStage';
/**
* 已写成诊断记录的回合失败:阶段 + 诊断正文。
*/
export type TurnFailed = { stage: FailureStage; detail: string };
@@ -1,6 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* 深层尚未 typed 的字符串错误。
*/
export type TurnFailedUnclassified = { detail: string };
@@ -3,11 +3,11 @@ import type { EnvironmentNotReady } from './EnvironmentNotReady';
import type { HostStateUnavailable } from './HostStateUnavailable';
import type { ModelCallFailed } from './ModelCallFailed';
import type { ProjectRootUnanchored } from './ProjectRootUnanchored';
import type { SuperErrorFromStringPlusStage } from './SuperErrorFromStringPlusStage';
import type { TimedOut } from './TimedOut';
import type { TransportClosed } from './TransportClosed';
import type { TurnFailed } from './TurnFailed';
import type { TurnFailedUnclassified } from './TurnFailedUnclassified';
import type { TurnInterrupted } from './TurnInterrupted';
import type { Unclassified } from './Unclassified';
/**
* 失败终态的可下发载荷(`turn.completed.status == "failed"` 时必有,其余终态没有)。
@@ -27,6 +27,6 @@ export type TurnFailure =
| ({ type: 'transportClosed' } & TransportClosed)
| ({ type: 'timedOut' } & TimedOut)
| ({ type: 'turnInterrupted' } & TurnInterrupted)
| ({ type: 'turnFailed' } & TurnFailed)
| ({ type: 'turnFailedUnclassified' } & TurnFailedUnclassified)
| ({ type: 'superErrorFromStringPlusStage' } & SuperErrorFromStringPlusStage)
| ({ type: 'unclassified' } & Unclassified)
| { type: 'hostDropped' };
@@ -0,0 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* 深层尚未 typed 的字符串错误:不知道发生在哪一段,只有一条宿主原文。
*/
export type Unclassified = { detail: string };
@@ -506,7 +506,7 @@ describe('DirectProject 聊天 reducer', () => {
type: 'turn.completed',
status: 'failed',
failure: {
type: 'turnFailedUnclassified',
type: 'unclassified',
detail: '模型服务暂不可用',
},
at: 1_000_900,
@@ -521,7 +521,7 @@ describe('DirectProject 聊天 reducer', () => {
type: 'turn.completed',
status: 'failed',
failure: {
type: 'turnFailedUnclassified',
type: 'unclassified',
detail: '模型服务暂不可用',
},
at: 9_900_000,
@@ -547,7 +547,7 @@ describe('DirectProject 聊天 reducer', () => {
event({
type: 'turn.completed',
status: 'failed',
failure: { type: 'turnFailedUnclassified', detail: '上一轮的失败' },
failure: { type: 'unclassified', detail: '上一轮的失败' },
at: 2_000_900,
userItemId: 'direct-codex:turn-1:user',
}),
@@ -627,7 +627,7 @@ describe('DirectProject 聊天 reducer', () => {
event({
type: 'turn.completed',
status: 'failed',
failure: { type: 'turnFailedUnclassified', detail: '第一轮失败' },
failure: { type: 'unclassified', detail: '第一轮失败' },
at: 2_000,
}),
]);
@@ -687,7 +687,7 @@ describe('DirectProject 聊天 reducer', () => {
event({
type: 'turn.completed',
status: 'failed',
failure: { type: 'turnFailedUnclassified', detail: '第一轮失败' },
failure: { type: 'unclassified', detail: '第一轮失败' },
at: 2_000,
}),
]);
@@ -696,7 +696,7 @@ describe('DirectProject 聊天 reducer', () => {
event({
type: 'turn.completed',
status: 'failed',
failure: { type: 'turnFailedUnclassified', detail: '第二轮失败' },
failure: { type: 'unclassified', detail: '第二轮失败' },
at: 4_000,
}),
]);