DirectProject 历史格式类失败给出专门提示并按不可重试处理
- direct_runtime.rs:新增 direct_project_history_shape_failure 白名单(历史记录类型无效 / 缺少 payload / 解析历史失败) - direct_codex_failure_recovery_hint 增加专门 hint,指向真实现象与动作:该类旧格式已被读侧兼容,仍失败说明记录不在白名单内,请检查项目诊断后修复该历史文件 - direct_codex_failure_is_retryable 对该类失败返回 false,前端不再显示「可直接重试」(同一份历史每次读得到同一结论) - 打开/读取类 IO 失败不在白名单内,仍按可重试处理 - 补断言:hint 文案、IO 失败边界、诊断 message 里的 retryable=false 与 sidecar 的 "retryable": false
This commit is contained in:
@@ -1786,6 +1786,9 @@ fn direct_codex_failure_recovery_hint(stage: DirectCodexFailureStage, error: &st
|
||||
{
|
||||
return "历史画布资源不满足安全恢复条件,请先在资源画布确认唯一可用的核心图集";
|
||||
}
|
||||
if direct_project_history_shape_failure(error) {
|
||||
return "项目对话历史存在本版本无法识别的记录,旧格式已兼容读取,请检查项目诊断后修复该历史文件再发送需求";
|
||||
}
|
||||
match stage {
|
||||
DirectCodexFailureStage::ArtPreparation => {
|
||||
"平台资源暂时无法完成准备,请稍后重试;如持续失败请检查项目诊断"
|
||||
@@ -1819,6 +1822,9 @@ fn direct_codex_failure_is_retryable(error: &str) -> bool {
|
||||
if direct_codex_error_is_mud_points_insufficient(error) {
|
||||
return false;
|
||||
}
|
||||
if direct_project_history_shape_failure(error) {
|
||||
return false;
|
||||
}
|
||||
![
|
||||
"private-external-editor-credential-storage-preparation-failed",
|
||||
"private-external-editor-credential-persistence-failed",
|
||||
@@ -1832,6 +1838,20 @@ fn direct_codex_failure_is_retryable(error: &str) -> bool {
|
||||
.any(|marker| error.contains(marker))
|
||||
}
|
||||
|
||||
/// DirectProject 历史文件里与“行形状”有关的失败:同一份文件每次读都会得到同一结果,
|
||||
/// 重试不会改变结论。IO 类失败(打开/读取目录)不在其中,那些仍按可重试处理。
|
||||
const DIRECT_PROJECT_HISTORY_SHAPE_FAILURE_MARKERS: &[&str] = &[
|
||||
"DirectProject 历史记录类型无效",
|
||||
"DirectProject 历史记录缺少 payload",
|
||||
"解析 DirectProject 历史失败",
|
||||
];
|
||||
|
||||
fn direct_project_history_shape_failure(error: &str) -> bool {
|
||||
DIRECT_PROJECT_HISTORY_SHAPE_FAILURE_MARKERS
|
||||
.iter()
|
||||
.any(|marker| error.contains(marker))
|
||||
}
|
||||
|
||||
fn direct_codex_error_is_mud_points_insufficient(error: &str) -> bool {
|
||||
let normalized = error.to_ascii_lowercase();
|
||||
error.contains("泥点余额不足")
|
||||
@@ -4549,6 +4569,22 @@ mod tests {
|
||||
assert!(!direct_codex_failure_is_retryable(error));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_project_history_shape_failure_has_explicit_non_retryable_guidance() {
|
||||
let error = "DirectProject 历史记录类型无效:$PROJECT_ROOT/.agent/conversations/project.jsonl";
|
||||
assert!(direct_project_history_shape_failure(error));
|
||||
assert_eq!(
|
||||
direct_codex_failure_recovery_hint(DirectCodexFailureStage::CodeGeneration, error),
|
||||
"项目对话历史存在本版本无法识别的记录,旧格式已兼容读取,请检查项目诊断后修复该历史文件再发送需求"
|
||||
);
|
||||
assert!(!direct_codex_failure_is_retryable(error));
|
||||
|
||||
// 历史文件的 IO 失败仍按可重试处理:它与行形状无关,重试可能成功。
|
||||
let io_error = "打开 DirectProject 历史失败:拒绝访问";
|
||||
assert!(!direct_project_history_shape_failure(io_error));
|
||||
assert!(direct_codex_failure_is_retryable(io_error));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_turn_id_is_strictly_normalized_and_bounded() {
|
||||
assert_eq!(
|
||||
@@ -6523,6 +6559,50 @@ mod tests {
|
||||
assert!(!diagnostic.contains("provider.example"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_failure_diagnostic_marks_project_history_shape_failure_as_not_retryable() {
|
||||
let root = tempfile::tempdir().expect("temp dir");
|
||||
init_local_game_project_at(root.path(), "direct-diagnostic", "直连诊断")
|
||||
.expect("init project");
|
||||
let history_path = root
|
||||
.path()
|
||||
.join(".agent/conversations/project.jsonl")
|
||||
.display()
|
||||
.to_string();
|
||||
let error = record_direct_codex_turn_failure(
|
||||
root.path(),
|
||||
DirectCodexTurnFailure::new(
|
||||
DirectCodexFailureStage::CodeGeneration,
|
||||
format!("DirectProject 历史记录类型无效:{history_path}"),
|
||||
),
|
||||
);
|
||||
|
||||
assert!(
|
||||
error.starts_with(
|
||||
"direct-codex-failure:v1 stage=code-generation retryable=false summary="
|
||||
),
|
||||
"{error}"
|
||||
);
|
||||
assert!(
|
||||
error.contains(
|
||||
";建议:项目对话历史存在本版本无法识别的记录,旧格式已兼容读取,请检查项目诊断后修复该历史文件再发送需求;"
|
||||
),
|
||||
"{error}"
|
||||
);
|
||||
assert!(error.ends_with("已保存脱敏项目诊断"), "{error}");
|
||||
|
||||
let diagnostics = root.path().join(".agent/runtime/direct-codex-diagnostics");
|
||||
let entries = std::fs::read_dir(&diagnostics)
|
||||
.expect("diagnostic directory")
|
||||
.filter_map(Result::ok)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(entries.len(), 1);
|
||||
let diagnostic = std::fs::read_to_string(entries[0].path().join("failure.json"))
|
||||
.expect("diagnostic sidecar");
|
||||
assert!(diagnostic.contains("\"retryable\": false"), "{diagnostic}");
|
||||
assert!(diagnostic.contains("旧格式已兼容读取"), "{diagnostic}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_failure_diagnostic_marks_ambiguous_canvas_identity_as_not_retryable() {
|
||||
let root = tempfile::tempdir().expect("temp dir");
|
||||
|
||||
Reference in New Issue
Block a user