资源编辑 typed 错误只给事实,前缀交给使用方
- ResourceEditError::to_user_msg 不再拼「资源编辑生成失败」:远端终态只回平台 error 原文,平台没给时统一说「服务器未返回错误信息」 - 桌面命令面 derive/resume_local_project_resource 自己补「资源编辑生成失败:」前缀,桌面 UI 文案不变,前缀落在它真正的使用者这一层 - 单测:typed 错误断言原文逐字透传与缺省兜底;抠图远端失败测试同口径更新 - decision-log 追加「前缀归属」决策(工具面各用各的前缀、桌面面用资源编辑生成失败、叶子错误不写死总结),pitfalls 同步口径
This commit is contained in:
@@ -562,7 +562,9 @@ pub(crate) async fn derive_local_project_resource(
|
||||
) -> Result<DeriveLocalProjectResourceResult, String> {
|
||||
let root = Path::new(input.project_path.trim());
|
||||
enforce_project_permission_policy(root, "asset.register")?;
|
||||
derive_local_project_resource_at(input).await
|
||||
derive_local_project_resource_at(input)
|
||||
.await
|
||||
.map_err(resource_edit_command_error)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -580,7 +582,15 @@ pub(crate) async fn resume_local_project_resource_edit(
|
||||
) -> Result<DeriveLocalProjectResourceResult, String> {
|
||||
let root = Path::new(input.project_path.trim());
|
||||
enforce_project_permission_policy(root, "asset.register")?;
|
||||
resume_local_project_resource_edit_at(input).await
|
||||
resume_local_project_resource_edit_at(input)
|
||||
.await
|
||||
.map_err(resource_edit_command_error)
|
||||
}
|
||||
|
||||
/// 桌面命令面给自己的资源编辑失败加前缀:typed 错误只带平台原文(或「服务器未返回错误信息」),
|
||||
/// 前缀是使用者的事——工具面用的是各工具自己的前缀,这里是桌面命令。
|
||||
fn resource_edit_command_error(cause: String) -> String {
|
||||
format!("资源编辑生成失败:{cause}")
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
+1
-1
@@ -714,7 +714,7 @@ async fn background_removal_remote_failure_keeps_manifest_without_result() {
|
||||
server_message: None
|
||||
}
|
||||
);
|
||||
assert_eq!(error.to_user_msg(), "资源编辑生成失败");
|
||||
assert_eq!(error.to_user_msg(), "服务器未返回错误信息");
|
||||
let manifest =
|
||||
read_existing_manifest_for_project(fixture.root()).expect("read unchanged manifest");
|
||||
assert_eq!(manifest.assets.len(), 1);
|
||||
|
||||
@@ -17,13 +17,16 @@ pub(crate) enum ResourceEditError {
|
||||
}
|
||||
|
||||
impl ResourceEditError {
|
||||
/// 面向用户与模型的文案。只有这一层知道「远端生成失败」该怎么说,
|
||||
/// 上层(工具层)原样透传,不再叠自己的前缀。
|
||||
/// 面向用户与模型的文案。
|
||||
///
|
||||
/// 这里只给事实:平台原文,或平台没给时的「服务器未返回错误信息」。「谁失败了」这类前缀由使用者
|
||||
/// 自己加(工具面用各工具自己的前缀,桌面命令面用「资源编辑生成失败」),这一层不替它们定调——
|
||||
/// 前缀写死在这里,非工具侧的调用方就只能看到一句没有信息量的总结。
|
||||
pub(crate) fn to_user_msg(&self) -> String {
|
||||
match self {
|
||||
Self::RemoteGenerationFailed { server_message } => match server_message {
|
||||
Some(server_message) => format!("资源编辑生成失败:{server_message}"),
|
||||
None => "资源编辑生成失败".to_string(),
|
||||
Some(server_message) => server_message.clone(),
|
||||
None => "服务器未返回错误信息".to_string(),
|
||||
},
|
||||
Self::Other(message) => message.clone(),
|
||||
}
|
||||
@@ -35,3 +38,26 @@ impl std::fmt::Display for ResourceEditError {
|
||||
formatter.write_str(&self.to_user_msg())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn remote_generation_failure_keeps_platform_text_verbatim() {
|
||||
let error = ResourceEditError::RemoteGenerationFailed {
|
||||
server_message: Some("provider detail".to_string()),
|
||||
};
|
||||
|
||||
assert_eq!(error.to_user_msg(), "provider detail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_generation_failure_without_message_blames_the_server() {
|
||||
let error = ResourceEditError::RemoteGenerationFailed {
|
||||
server_message: None,
|
||||
};
|
||||
|
||||
assert_eq!(error.to_user_msg(), "服务器未返回错误信息");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
- 决策(原文边界):平台原文只进当次错误文案,仍不进账本(`terminal_failure_code` 的写入边界与既有断言不变)。平台 `user_visible_external_generation_error` 已对四种 kind 做 sanitize,图片/视频两种原样透出——与同 wire 的 `canvas_generation.rs` 口径一致,要收边界应改服务端。
|
||||
- 决策(命名与落点):尚未 typed 化的变体叫 `Other`,不叫 `Message`(后者分不清是「已渲染文案」还是「原始消息」);新错误单独放 `project/resource_editor/error.rs`,不再往主文件里塞类型定义。
|
||||
- 决策(工具层承载):`RemoteGenerationFailed` 不能到工具层又被压回一句字符串。共用载体放 `agent/tool/error.rs` 的 `RemoteResourceEditFailure { serverMessage }`(两个工具共用的文案只写一份),`CreateOrDeriveResourceError` / `RemoveBackgroundError` 各加 `RemoteGenerationFailed(RemoteResourceEditFailure)` 变体;翻译用显式 `from_resource_edit_error`,不用 `impl From<ResourceEditError>`,远端终态进 `RemoteGenerationFailed`、其余 `Other` 仍落回各工具原有的「失败:<文案>」变体。这样诊断 sidecar 的 `error` 字段(typed enum 整体序列化)天然带上平台原文,LLM 侧拿到的 `message` 也带上。
|
||||
- 决策(前缀归属,2026-10-02 追加):叶子错误只给事实,不给「谁失败了」的总结前缀。`RemoteResourceEditFailure::to_user_msg` 与 `ResourceEditError::to_user_msg` 都只返回平台 `error` 原文;平台没给时统一说「服务器未返回错误信息」,不再说「资源编辑生成失败」这种没有信息量的总结。前缀由使用者自己加:`agc_create_or_derive_resource` 用「生成或派生资源失败:」、`agc_remove_background` 用「抠图失败:」、桌面命令面用「资源编辑生成失败:」。同一份 typed 错误因此可以同时服务工具面(前缀各随其工具)与桌面面(保留原有文案)。
|
||||
- 决策(凭据作用域的 error 类型):`with_direct_editor_api_credentials` 原本把操作限定成 `Result<_, String>`,会把 typed 错误提前压掉。新增 `with_direct_editor_api_credentials_as(operation, credentials_error)` 保留调用方 error 类型,凭据解析失败由调用方显式翻译(这里传 `ResourceEditError::Other`),旧名保持 `String` 语义、零改动。
|
||||
- 决策(future 装箱):`handle_direct_tool_bridge` 的状态机在调试测试线程的默认栈上已经贴着上限,资源编辑 arm 直接内联会顶穿(`bridge_write_file_waits_on_the_blocking_pool_instead_of_a_runtime_worker` 栈溢出)。桥里四个资源编辑 await 点用 `Box::pin` 只留指针进外层状态机;这是体积问题,不是错误用 `Box`。
|
||||
- 改动范围:`apps/ai-game-creator-shell/src-tauri/src/project/resource_editor.rs`、`project/resource_editor/error.rs`(新)、`agent/tool/error.rs`、`agent/tool/create_or_derive_resource/error.rs`、`agent/tool/remove_background/error.rs`、`agent/direct_tool_bridge.rs`、`assets.rs`、`docs/project-memory/shared-memory/pitfalls.md`。
|
||||
|
||||
@@ -340,7 +340,7 @@ Copy Artifact 插件在**非 SYSTEM 认证**下按「认证用户」判权:只
|
||||
|
||||
- 现象:「生成背景音乐」再次提交 0.1 秒就失败,卡片只有 `remote-terminal-failed: 远端资源编辑已明确失败,不允许再次请求`,既没有原因也没有下一步。
|
||||
- 原因:上一次同 `operationId` 的请求被平台确定性拒绝(HTTP 400 或任务 `failed`)后,账本落到 `remote-failed`,之后所有重试都在 `ensure_resource_edit_phase_resumable` 失败关闭;唯一出口是「待恢复资源编辑」里的移出恢复队列,但终态文案没有指向它。
|
||||
- 处理:终态文案带出稳定失败码,并明确「先在待恢复资源编辑中把它移出恢复队列」;上游失败原文仍不写入账本(只存分类码),首次失败的原始拒绝说明继续由当次错误文案承担——轮询终态这条路由此改成把平台 `error` 原文装进 `ResourceEditError::RemoteGenerationFailed` 原样带出(2026-10-02),工具层再用 `RemoteResourceEditFailure` 原样传给模型与用户卡片,不再压成「资源编辑生成失败」一句;第一句失败文案不再带 `remote-terminal-failed:` 前缀。
|
||||
- 处理:终态文案带出稳定失败码,并明确「先在待恢复资源编辑中把它移出恢复队列」;上游失败原文仍不写入账本(只存分类码),首次失败的原始拒绝说明继续由当次错误文案承担——轮询终态这条路由此改成把平台 `error` 原文装进 `ResourceEditError::RemoteGenerationFailed` 原样带出(2026-10-02),工具层经 `RemoteResourceEditFailure` 转发时只保留原文、平台没给就说「服务器未返回错误信息」,前缀由使用它的工具/命令自己加(不再统一压成「资源编辑生成失败」一句,也不再多一层无信息前缀);第一句失败文案不再带 `remote-terminal-failed:` 前缀。
|
||||
- 验证:`remote_failed_status_is_terminal_and_can_only_be_archived` 断言失败文案带出平台 `error` 原文、同时账本序列化不含原文;`background_removal_remote_failure_keeps_manifest_without_result` 覆盖平台没给 `error` 时的兜底文案;`submission_bad_request_is_terminal_while_gateway_failure_requires_reconciliation` 等资源编辑用例继续通过。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/project/resource_editor.rs`。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user