宿主:封口返修要求改成 typed 控制流,不再写成失败
- 新增 `HostOutcomeText`:封口复核的"继续返修批次"用独立变体表达,不再伪装成 `LlmError::InvalidRequest` - 新增 `DirectTurnRunFailure`:app-server 回合结果区分"真失败"与"返修控制流",`RepairRequired` 不写终态 - `direct_turn_terminal_write` 收口终态写出:返修要求跳过,真失败从 typed 错误投影 `kind` / `message` - 早退取得宿主收尾事实的分支只认真失败,返修控制流不再被中断成一次收束 - `DirectTurnError` 新增 `RepairRequired` 变体并重新生成前端绑定 - 返修循环同时消费 `ReviewRequired` / `RepairRequired`,次数上限仍留在产生侧 - 补单测:返修要求不写终态、真失败投影成 transport-failed、正常收尾不带载荷
This commit is contained in:
@@ -78,12 +78,41 @@ pub(super) enum HostOutcome {
|
||||
RepairRequired,
|
||||
}
|
||||
|
||||
pub(super) fn outcome_text(outcome: HostOutcome) -> Result<String, platform_llm::LlmError> {
|
||||
/// [`HostOutcome`] 的文本投影。
|
||||
///
|
||||
/// 返修要求([`HostOutcome::RepairRequired`])用**自己的变体**表达:它是控制流("继续当前返修
|
||||
/// 批次"),不是失败。以前它伪装成 `LlmError::InvalidRequest("validation-source-changed: …")`,
|
||||
/// 于是和真失败走同一条投影——终态被判成 `failed`、界面收到一条用户可见的失败说明。
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) enum HostOutcomeText {
|
||||
/// 正常收尾:可展示的回复 / 交付报告文本。
|
||||
Report(String),
|
||||
/// 封口复核要求继续当前返修批次(控制流,不是失败)。
|
||||
RepairRequired { detail: String },
|
||||
}
|
||||
|
||||
/// 返修要求写回提示词时用的说明。
|
||||
pub(super) const HOST_OUTCOME_REPAIR_REQUIRED_DETAIL: &str =
|
||||
"宿主收尾复核发现输入或证据变化,请读取交付状态后继续当前返修批次";
|
||||
|
||||
impl HostOutcomeText {
|
||||
/// 投影成这一轮的收尾结果:正常报告是文本,返修要求是控制流(走 `Err` 侧自己的变体)。
|
||||
pub(super) fn into_run_result(self) -> Result<String, super::DirectTurnRunFailure> {
|
||||
match self {
|
||||
Self::Report(text) => Ok(text),
|
||||
Self::RepairRequired { detail } => {
|
||||
Err(super::DirectTurnRunFailure::RepairRequired { detail })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn outcome_text(outcome: HostOutcome) -> HostOutcomeText {
|
||||
match outcome {
|
||||
HostOutcome::Report(report) => Ok(report),
|
||||
HostOutcome::RepairRequired => Err(platform_llm::LlmError::InvalidRequest(
|
||||
"validation-source-changed: 宿主收尾复核发现输入或证据变化,请读取交付状态后继续当前返修批次".into(),
|
||||
)),
|
||||
HostOutcome::Report(report) => HostOutcomeText::Report(report),
|
||||
HostOutcome::RepairRequired => HostOutcomeText::RepairRequired {
|
||||
detail: HOST_OUTCOME_REPAIR_REQUIRED_DETAIL.to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5021,7 +5021,13 @@ async fn run_direct_game_creator_turn_inner(
|
||||
Ok(Some(report)) => break Ok(report),
|
||||
Ok(None) => break Ok(value),
|
||||
// 返修要求是控制流,不是失败:把要求写回 prompt 再跑一轮。
|
||||
Err(DirectTurnError::ReviewRequired { detail }) => {
|
||||
// `RepairRequired` 是同一族的第二条来源(app-server 封口复核),处理完全一样;
|
||||
// 两条路的次数上限都在产生侧(交付复核 `ledger.max_runs`、执行账本的批次上限),
|
||||
// 这里不另设计数,否则会把本来能收敛的长返修提前掐断。
|
||||
Err(
|
||||
DirectTurnError::ReviewRequired { detail }
|
||||
| DirectTurnError::RepairRequired { detail },
|
||||
) => {
|
||||
emitter.emit("running",Some("host-review"),Some("宿主复核发现必需证据尚未齐备,正在按冻结范围继续处理".into()),None);
|
||||
feedback_prompt = format!(prompt_text!("direct.deliveryFeedback"),detail=detail);
|
||||
}
|
||||
@@ -5084,7 +5090,12 @@ async fn run_direct_game_creator_turn_inner(
|
||||
Ok(Some(report)) => { response = Some(report); break; }
|
||||
Ok(None) => { response = Some(value); break; }
|
||||
// 返修要求是控制流,不是失败:把要求写回 prompt 再跑一轮。
|
||||
Err(DirectTurnError::ReviewRequired { detail }) => {
|
||||
// `RepairRequired` 是同一族的第二条来源(app-server 封口复核),处理完全一样;
|
||||
// 次数上限在产生侧(见流式分支同一处注释),这里不另设计数。
|
||||
Err(
|
||||
DirectTurnError::ReviewRequired { detail }
|
||||
| DirectTurnError::RepairRequired { detail },
|
||||
) => {
|
||||
feedback_prompt = format!(prompt_text!("direct.deliveryFeedback"),detail=detail);
|
||||
}
|
||||
// 回合级失败原样带出:typed 分类决定诊断摘要与建议,不再降级成文本。
|
||||
|
||||
@@ -403,6 +403,10 @@ pub(crate) enum DirectTurnError {
|
||||
/// 宿主复核要求继续本轮的返修批次 —— **控制流,不是失败**:
|
||||
/// 交付模块用它把"还缺证据"交给下一步,界面不应该看到失败。
|
||||
ReviewRequired { detail: String },
|
||||
/// 宿主**封口**复核要求继续当前返修批次(app-server 收尾的 `HostOutcome::RepairRequired`)
|
||||
/// —— **控制流,不是失败**:与 [`DirectTurnError::ReviewRequired`] 同一族,只是产生点在收尾
|
||||
/// 阶段而不是交付复核。调用方把它写回提示词继续跑:不写终态、不进失败载荷、不上报。
|
||||
RepairRequired { detail: String },
|
||||
/// 已经写成诊断记录的回合失败:`detail` 是诊断正文(阶段 / 分类 / 建议 / 详情引用)。
|
||||
TurnFailed {
|
||||
stage: DirectCodexFailureStage,
|
||||
@@ -464,6 +468,7 @@ impl DirectTurnError {
|
||||
| Self::TimedOut { .. }
|
||||
| Self::TurnInterrupted { .. }
|
||||
| Self::ReviewRequired { .. }
|
||||
| Self::RepairRequired { .. }
|
||||
| Self::TurnFailed { .. }
|
||||
| Self::TurnFailedUnclassified { .. } => false,
|
||||
}
|
||||
@@ -647,7 +652,9 @@ impl fmt::Display for DirectTurnError {
|
||||
"执行通道已断开,不能自动重放未确认操作:{diagnostic}"
|
||||
),
|
||||
Self::TimedOut { deadline } => formatter.write_str(deadline.message()),
|
||||
Self::ReviewRequired { detail } => formatter.write_str(detail),
|
||||
Self::ReviewRequired { detail } | Self::RepairRequired { detail } => {
|
||||
formatter.write_str(detail)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -26,5 +26,6 @@ export type DirectTurnError =
|
||||
| { type: 'timedOut'; deadline: DirectTurnDeadline }
|
||||
| { type: 'turnInterrupted'; detail: string }
|
||||
| { type: 'reviewRequired'; detail: string }
|
||||
| { type: 'repairRequired'; detail: string }
|
||||
| { type: 'turnFailed'; stage: DirectCodexFailureStage; detail: string }
|
||||
| { type: 'turnFailedUnclassified'; detail: string };
|
||||
|
||||
Reference in New Issue
Block a user