From 214055e319ce2afc007f06bfde4a44b859d70ac9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 1 Oct 2026 18:31:21 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=99=E6=A8=A1=E5=9E=8B=E7=9A=84=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E7=BB=93=E6=9E=9C=E4=B8=8D=E5=86=8D=E8=84=B1=E6=95=8F?= =?UTF-8?q?=E6=88=96=E6=88=AA=E6=96=AD=EF=BC=8C=E8=84=B1=E6=95=8F=E5=8F=AA?= =?UTF-8?q?=E7=95=99=E5=9C=A8=E8=AF=8A=E6=96=AD=E4=BE=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - bridge_tool_failure 原样回传 typed 错误的 message,去掉 redact_agent_runtime_error - compose_direct_tool_outcome 只在写诊断时按 ToolFailure::redact_limit 脱敏截断,模型面用原文 - 编辑器执行与 Cocos 执行的成功回执去掉脱敏/路径替换,report 原样进工具结果 - 账号素材导入的 failures、生成/抠图/美术准备的 warnings 不再脱敏 - 删除随之失效的 bridge_safe_warning_messages 及其断言脱敏的测试 --- .../src-tauri/src/agent/direct_tool_bridge.rs | 103 ++++-------------- 1 file changed, 23 insertions(+), 80 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs index 922b4cacb..cc703f63b 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_tool_bridge.rs @@ -544,7 +544,7 @@ fn bridge_append_tool_images(result: &mut Value, images: Vec) { /// 工具失败在 dispatch 边界上的唯一载体:具体类型泛型进、具体字段出,不枚举、不 Box。 /// /// `message` / `redact_limit` 来自具体 [`ToolFailure`],`error` 是它的原样序列化。 -/// 结构化事实与给模型的那句话都靠它带出 dispatch,composer 一处写结果与诊断。 +/// `message` 原样进给模型的工具结果;`redact_limit` 只用于写诊断的那一份。 #[derive(Debug)] pub(crate) struct ToolCallError { pub message: String, @@ -566,11 +566,11 @@ impl From<&T> for ToolCallError { } /// 失败结果:`isError` 只在 composer 的分支上出现一次,工具与桥都不再手传这个布尔。 -fn bridge_tool_failure(root: &Path, error: &ToolCallError) -> Value { - let mut result = bridge_tool_result( - redact_agent_runtime_error(root, &error.message, error.redact_limit), - Vec::new(), - ); +/// +/// 文案原样给模型(是 typed 错误自己写的那一句),桥不脱敏、不截断;脱敏与预算只发生在 +/// 诊断侧(见 [`compose_direct_tool_outcome`])。 +fn bridge_tool_failure(error: &ToolCallError) -> Value { + let mut result = bridge_tool_result(error.message.clone(), Vec::new()); result["isError"] = Value::Bool(true); result } @@ -593,18 +593,22 @@ fn compose_direct_tool_outcome( value } Err(error) => { + // 给模型的失败结果不脱敏、不截断:模型看到的就是 typed 错误写的原文。 + // 写诊断的那一份才脱敏,并按工具自己声明的 `redact_limit` 截断。 + let diagnostic_message = + redact_agent_runtime_error(&state.root, &error.message, error.redact_limit); // 诊断事件按回合归属:并发排障要能从错误记录认出是哪一轮调的工具。 // 桥没被回合授权(外部客户端工具桥)或授权状态不可用时如实记为无回合。 let client_turn_id = state.active_client_turn_id().ok().flatten(); - // `code` 只放稳定的工具名,供并发排障定位"是哪个工具、哪一轮";文案是 typed 错误 - // 写好的那一句,原始 typed 错误整体进 `error`,入参与上下文进 metadata。 + // `code` 只放稳定的工具名,供并发排障定位"是哪个工具、哪一轮";原始 typed 错误 + // 整体进 `error`,入参与上下文进 metadata。 let _ = persist_agent_runtime_error( &state.root, client_turn_id.as_deref(), "agc-tools", "tool-execution", tool, - &error.message, + &diagnostic_message, error.error.clone(), None, json!({ @@ -614,7 +618,7 @@ fn compose_direct_tool_outcome( "dispatchDenied": dispatch_denied, }), ); - let mut value = bridge_tool_failure(&state.root, &error); + let mut value = bridge_tool_failure(&error); bridge_append_tool_images(&mut value, error.images); value } @@ -2118,7 +2122,7 @@ async fn bridge_import_account_assets( "source": source, }) })), - Err(error) => failures.push(redact_agent_runtime_error(&state.root, &error, 360)), + Err(error) => failures.push(error), } } if !local_paths.is_empty() { @@ -2135,7 +2139,7 @@ async fn bridge_import_account_assets( "source": "local", }) })), - Err(error) => failures.push(redact_agent_runtime_error(&state.root, &error, 360)), + Err(error) => failures.push(error), } } // Direct tools run outside the normal Runtime action loop. Keep the @@ -2191,8 +2195,8 @@ fn bridge_completed_resource_result( "sourceResourceId": result.source_resource_id, "committedProjectRevision": result.committed_project_revision, "resource": bridge_registered_resource(asset, true), - "warnings": bridge_safe_warning_messages(root, warnings), - "sliceWarnings": bridge_safe_warning_messages(root, slice_warnings), + "warnings": warnings, + "sliceWarnings": slice_warnings, })) } @@ -2618,25 +2622,16 @@ fn bridge_art_resources( .collect()) } -fn bridge_safe_warning_messages(root: &Path, warnings: Vec) -> Vec { - warnings - .into_iter() - .map(|warning| redact_agent_runtime_error(root, &warning, 480)) - .collect() -} - async fn bridge_prepare_game_art_validated( root: &Path, brief: String, mode: DirectTaonierArtPreparationMode, ) -> Result { - let mut package = with_direct_editor_api_credentials(ensure_direct_taonier_art_package_at( + let package = with_direct_editor_api_credentials(ensure_direct_taonier_art_package_at( root, &brief, mode, )) .await .map_err(|cause| PrepareGameArtError::PackageGenerationFailed { cause })?; - package.warnings = bridge_safe_warning_messages(root, package.warnings); - package.slice_warnings = bridge_safe_warning_messages(root, package.slice_warnings); let resources = bridge_art_resources(root, &package.asset_paths, &package.slice_paths) .map_err(|cause| PrepareGameArtError::ArtResourcesUnreadable { cause })?; let images = package @@ -3171,8 +3166,8 @@ async fn bridge_generate_image( "taskId": generated.task_id, }, "resources": resources, - "warnings": generated.warning.map(|warning| bridge_safe_warning_messages(&state.root, vec![warning])).unwrap_or_default(), - "sliceWarnings": generated.slice_warning.map(|warning| bridge_safe_warning_messages(&state.root, vec![warning])).unwrap_or_default(), + "warnings": generated.warning.map(|warning| vec![warning]).unwrap_or_default(), + "sliceWarnings": generated.slice_warning.map(|warning| vec![warning]).unwrap_or_default(), "sliceMode": generated.slice_mode, "gridX": generated.grid_x, "gridY": generated.grid_y, @@ -3638,10 +3633,7 @@ async fn bridge_editor_execute( report: response, }); } - Ok(bridge_tool_result( - redact_agent_runtime_error(&state.root, &response.to_string(), 32_000), - Vec::new(), - )) + Ok(bridge_tool_result(response.to_string(), Vec::new())) } Ok(Err(error)) => Err(error), Err(_) => Err(EditorExecuteError::ExecutionUnconfirmed { editor }), @@ -3850,18 +3842,7 @@ async fn bridge_cocos_call( screenshots: images, }); } - Ok(bridge_tool_result( - redact_agent_runtime_project_paths( - &state.root, - &report.to_string(), - if operation.is_some() { - 2 * 1024 * 1024 - } else { - 32_000 - }, - ), - images, - )) + Ok(bridge_tool_result(report.to_string(), images)) } failed => { let (is_uncertain, error) = match failed { @@ -6359,42 +6340,4 @@ mod tests { assert_eq!(projection["kind"], "ui-design"); assert_eq!(projection["category"], "ui-interaction"); } - - #[test] - fn bridge_success_warnings_are_redacted_before_serialization() { - let root = tempfile::tempdir().expect("warning redaction root"); - let private_path = root.path().join("assets/private.png"); - let warnings = bridge_safe_warning_messages( - root.path(), - vec![ - format!( - "写入失败:{};api_key=private-warning-key", - private_path.display() - ), - "Authorization=Basic private-authorization".to_string(), - "Cookie=session_id=private-cookie".to_string(), - "token: private-token".to_string(), - r#"{"Authorization":"Basic private-json","token" = "private-json-token"}"# - .to_string(), - ], - ); - let serialized = serde_json::to_string(&warnings).expect("serialize safe warnings"); - assert!(!serialized.contains(&root.path().to_string_lossy().to_string())); - for private in [ - "private-warning-key", - "private-authorization", - "private-cookie", - "private-token", - "private-json", - "private-json-token", - "api_key", - "Authorization", - "Cookie", - ] { - assert!( - !serialized.contains(private), - "leaked {private}: {serialized}" - ); - } - } }