宿主与前端:用户可见文案不再带诊断引用,失败也进错误上报池
- `record_direct_codex_failure` 的收口文案去掉 `;详情:<path>`,诊断 sidecar 与应用日志照写 - 同一出口把最终文案送进错误上报池:命令接单化后前端 catch 只剩"接单被拒",池不能只靠前端填 - 删除 `persist_direct_codex_failure_context`:失败说明本轮不写进项目历史,留 TODO 记录以后"进历史但不喂模型"的通道 - 删除只服务详情展开的 IPC `read_agent_runtime_error_detail` 及其注册 - 前端去掉 `详情:` 正则与第二次读取,横幅只显示一句话 - 同步命令边界与相关测试注释
This commit is contained in:
@@ -2162,7 +2162,8 @@ fn direct_codex_error_feedback_prompt(error: &str, attempt: usize) -> String {
|
||||
/// ([`DirectTurnError::is_reportable`],宿主 / 环境事实)。
|
||||
///
|
||||
/// 返回给用户看的那行 `direct-codex-failure:v2 ...` 文本:它是这一轮(或这次拒绝)的收口说明,
|
||||
/// 事件载荷、横幅与项目历史共用同一份,命令边界也只序列化它一次。
|
||||
/// 事件载荷与横幅共用同一份,命令边界也只序列化它一次。诊断文件的引用**不进**这份文案:
|
||||
/// 界面不再展开详情,线索只留在宿主侧(`.agent/runtime/errors`、应用日志与错误上报池)。
|
||||
pub(crate) fn record_direct_codex_failure(
|
||||
root: &Path,
|
||||
failure: &DirectTurnError,
|
||||
@@ -2211,7 +2212,8 @@ pub(crate) fn record_direct_codex_failure(
|
||||
};
|
||||
// 诊断 code 仍走共享的 runtime_error 分类(它是跨链路的持久化标签,不是流程判据)。
|
||||
let error_code = classify_direct_codex_error(&detail);
|
||||
let unified_detail_ref = persist_agent_runtime_error(
|
||||
// sidecar 照写,但引用不进用户可见文案。
|
||||
let _ = persist_agent_runtime_error(
|
||||
root,
|
||||
client_turn_id,
|
||||
"direct-codex",
|
||||
@@ -2226,10 +2228,9 @@ pub(crate) fn record_direct_codex_failure(
|
||||
"legacyDiagnosticWritten": diagnostic_written,
|
||||
}),
|
||||
)
|
||||
.ok()
|
||||
.map(|event| event.detail_ref);
|
||||
format!(
|
||||
"direct-codex-failure:v2 stage={} code={} retryable={} summary={};建议:{};{}{}",
|
||||
.ok();
|
||||
let public_text = format!(
|
||||
"direct-codex-failure:v2 stage={} code={} retryable={} summary={};建议:{};{}",
|
||||
stage.id(),
|
||||
error_code,
|
||||
retryable,
|
||||
@@ -2238,18 +2239,19 @@ pub(crate) fn record_direct_codex_failure(
|
||||
.unwrap_or("未提供可安全展示的详细原因"),
|
||||
recovery_hint,
|
||||
diagnostics_suffix,
|
||||
unified_detail_ref
|
||||
.map(|path| format!(";详情:{path}"))
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
);
|
||||
// 失败也进错误上报池:命令接单化之后前端 catch 只剩"接单被拒",池不能只靠前端填。
|
||||
// 两条通道同时上报也不会变成两条——池按 fingerprint 合并同一份文案。
|
||||
let _ = crate::error_report::report_agent_runtime_error("direct-codex", &public_text);
|
||||
public_text
|
||||
}
|
||||
|
||||
/// 命令边界的错误文本:可留痕的调用级拒绝在这里补一份运行错误诊断(返回串因此带 `详情:` 引用),
|
||||
/// 命令边界的错误文本:可留痕的调用级拒绝在这里补一份运行错误诊断(文案里不带诊断引用),
|
||||
/// 其余只输出 [`DirectTurnError`] 的 `Display`。
|
||||
///
|
||||
/// 分层改成 typed 之前,这几条"宿主 / 环境事实"是在回合失败通道里被写进诊断的;分层之后它们不再
|
||||
/// 进那条通道,留痕与界面的 `详情:` 展开都在这里补回来。GUI 命令与 CLI 边界共用这一份,禁止在各自
|
||||
/// 边界再写一套判据;回合级失败已在上游写过诊断,这里直接放行。
|
||||
/// 进那条通道,留痕在这里补回来。GUI 命令与 CLI 边界共用这一份,禁止在各自边界再写一套判据;
|
||||
/// 回合级失败已在上游写过诊断,这里直接放行。
|
||||
pub(crate) fn direct_turn_error_boundary_text(
|
||||
root: &Path,
|
||||
client_turn_id: Option<&str>,
|
||||
@@ -2261,19 +2263,6 @@ pub(crate) fn direct_turn_error_boundary_text(
|
||||
record_direct_codex_failure(root, &failure, client_turn_id)
|
||||
}
|
||||
|
||||
fn persist_direct_codex_failure_context(
|
||||
root: &Path,
|
||||
client_turn_id: &str,
|
||||
error: &str,
|
||||
) -> Result<(), String> {
|
||||
let item = direct_project_local_message_item(
|
||||
"assistant",
|
||||
error,
|
||||
Some(&format!("direct-codex:{client_turn_id}:failure")),
|
||||
)?;
|
||||
append_direct_project_history_item_at(root, &item)
|
||||
}
|
||||
|
||||
fn direct_taonier_art_generation_runtime_context(
|
||||
root: &Path,
|
||||
output_path: &str,
|
||||
@@ -4649,12 +4638,9 @@ async fn run_direct_game_creator_turn_at_with_creation_type_and_emitter(
|
||||
&failure,
|
||||
turn_emitter.map(|emitter| emitter.turn_id()),
|
||||
);
|
||||
if let Some(emitter) = turn_emitter {
|
||||
// Persist the safe terminal projection so the next DirectProject
|
||||
// turn can answer a diagnostic question from evidence instead of
|
||||
// guessing or starting another playtest.
|
||||
let _ = persist_direct_codex_failure_context(root, emitter.turn_id(), &error);
|
||||
}
|
||||
// TODO(Direct 命令接单化):失败说明本轮不写进项目历史——重进项目只会看到那条没有回复的
|
||||
// 用户消息,原因只在当轮界面与宿主诊断里。以后要给"进历史但不喂模型"的条目留一条通道
|
||||
// (见 `docs/adr/【ADR】DirectProject命令接单化-2026-09-23.md` 的备选方案第 3 条)。
|
||||
if let Some(emitter) = turn_emitter {
|
||||
// 失败说明也是这一回合的内容:按出现顺序追加到回合流末尾,
|
||||
// 这样"流里已经是完整内容"这一点对失败回合同样成立。
|
||||
@@ -7936,8 +7922,8 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// 可留痕的调用级拒绝(宿主 / 环境事实)在命令边界补一份运行错误诊断,返回串带 `详情:` 引用:
|
||||
/// 这是分层改 typed 之前的行为,前端横幅的展开逻辑按这个引用工作。
|
||||
/// 可留痕的调用级拒绝(宿主 / 环境事实)在命令边界补一份运行错误诊断,但返回串不再带引用:
|
||||
/// 界面不展开详情,线索只留在 `.agent/runtime/errors`、应用日志与错误上报池。
|
||||
#[test]
|
||||
fn reportable_call_rejection_writes_a_diagnostic_at_the_boundary() {
|
||||
let parent = tempfile::tempdir().expect("temp dir");
|
||||
@@ -7952,7 +7938,8 @@ mod tests {
|
||||
},
|
||||
);
|
||||
|
||||
assert!(text.contains("详情:.agent/runtime/errors/"), "{text}");
|
||||
assert!(text.contains("direct-codex-failure:v2"), "{text}");
|
||||
assert!(!text.contains("详情:"), "{text}");
|
||||
let entries = std::fs::read_dir(root.join(".agent/runtime/errors"))
|
||||
.expect("runtime error directory")
|
||||
.filter_map(Result::ok)
|
||||
|
||||
@@ -31,8 +31,8 @@ pub(crate) fn normalize_direct_client_turn_id(
|
||||
/// DirectProject 聊天命令:对外仍然是 `Result<String, String>`。
|
||||
///
|
||||
/// 字符串只在这里生成一次;前端拿到的仍是"一句给用户看的话",而 Rust 侧从命令入口到宿主出口全程
|
||||
/// 只传 typed 错误。可留痕的调用级拒绝(宿主 / 环境事实)在这里补一份运行错误诊断,返回串因此带上
|
||||
/// `详情:` 引用——界面横幅的展开逻辑按这个引用工作。
|
||||
/// 只传 typed 错误。可留痕的调用级拒绝(宿主 / 环境事实)在这里补一份运行错误诊断,但返回串不再
|
||||
/// 带诊断引用——界面不展开详情,线索只在宿主侧。
|
||||
///
|
||||
// TODO(Direct 命令接单化,未实施):现在这个命令 await 整轮,于是"命令边界"承担了不属于它的角色——
|
||||
// 回合失败的文案要靠这条 Err 回到界面,认证失败重试也只能挂在它上面。目标形状(草案见
|
||||
|
||||
@@ -5873,38 +5873,6 @@ pub(crate) async fn read_direct_project_conversation(
|
||||
.map_err(|error| format!("读取 DirectProject 历史后台任务失败:{error}"))?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn read_agent_runtime_error_detail(
|
||||
project_path: String,
|
||||
detail_ref: String,
|
||||
) -> Result<String, String> {
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
let root = Path::new(project_path.trim());
|
||||
enforce_project_permission_policy(root, "conversation.read")?;
|
||||
let relative = detail_ref.trim();
|
||||
let Some(file_name) = relative.strip_prefix(".agent/runtime/errors/") else {
|
||||
return Err("错误诊断引用不在项目错误目录内".to_string());
|
||||
};
|
||||
if file_name.is_empty()
|
||||
|| file_name.contains(['/', '\\'])
|
||||
|| file_name.contains("..")
|
||||
|| !file_name.ends_with(".json")
|
||||
{
|
||||
return Err("错误诊断引用格式无效".to_string());
|
||||
}
|
||||
let path = root.join(relative);
|
||||
prepare_game_creator_private_path_for_read(&path, false, "统一错误诊断")?;
|
||||
let bytes = std::fs::read(&path).map_err(|error| format!("读取错误诊断失败:{error}"))?;
|
||||
if bytes.len() > 16 * 1024 {
|
||||
return Err("错误诊断超过读取上限".to_string());
|
||||
}
|
||||
let text = String::from_utf8(bytes).map_err(|_| "错误诊断不是 UTF-8 文本".to_string())?;
|
||||
Ok(redact_agent_runtime_error(root, &text, 16 * 1024))
|
||||
})
|
||||
.await
|
||||
.map_err(|error| format!("读取统一错误诊断后台任务失败:{error}"))?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn list_game_creator_direct_active_turns(
|
||||
) -> Result<Vec<DirectActiveTurnSnapshot>, String> {
|
||||
|
||||
@@ -2761,7 +2761,6 @@ fn main() {
|
||||
archive_game_creator_agent_session,
|
||||
read_local_conversation,
|
||||
read_direct_project_conversation,
|
||||
read_agent_runtime_error_detail,
|
||||
list_game_creator_direct_active_turns,
|
||||
subscribe_direct_project_thread,
|
||||
consume_direct_project_thread,
|
||||
|
||||
+3
-15
@@ -610,22 +610,10 @@ export function useDirectProjectChatController({
|
||||
}
|
||||
void captureAgentRuntimeError(error, DIRECT_CODEX_AGENT_ID);
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
let persistedDetail = '';
|
||||
const detailRef = message.match(
|
||||
/详情:(\.agent\/runtime\/errors\/[^\s;]+)/,
|
||||
)?.[1];
|
||||
if (detailRef) {
|
||||
try {
|
||||
persistedDetail = await invoke<string>(
|
||||
'read_agent_runtime_error_detail',
|
||||
{ projectPath: nextProjectPath, detailRef },
|
||||
);
|
||||
} catch {
|
||||
persistedDetail = '';
|
||||
}
|
||||
}
|
||||
// 不再展开诊断详情:文案里没有引用,前端也不去读那份文件。线索留在
|
||||
// `.agent/runtime/errors`、应用日志与错误上报池里,界面只显示这一句话。
|
||||
const visibleMessage = projectRuntimeVisibleError(
|
||||
persistedDetail ? `${message}\n\n${persistedDetail}` : message,
|
||||
message,
|
||||
'陶泥儿智能创作',
|
||||
true,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user