@@ -341,9 +341,7 @@ fn is_read_tool(name: &str) -> bool {
341341/// Tool names are namespaced as `{skill_id}__{tool_name}`.
342342/// Read-only tools are excluded — their data comes from the memory layer (Step 2 context recall).
343343#[ cfg( not( any( target_os = "android" , target_os = "ios" ) ) ) ]
344- fn discover_tools (
345- engine : & crate :: runtime:: qjs_engine:: RuntimeEngine ,
346- ) -> Vec < serde_json:: Value > {
344+ fn discover_tools ( engine : & crate :: runtime:: qjs_engine:: RuntimeEngine ) -> Vec < serde_json:: Value > {
347345 engine
348346 . all_tools ( )
349347 . into_iter ( )
@@ -436,15 +434,9 @@ pub async fn chat_send(
436434 chat_state_arc. remove ( & thread_id_clone) ;
437435
438436 if let Err ( e) = result {
439- let _ = app_clone. emit (
440- "chat:error" ,
441- ChatErrorEvent {
442- thread_id : thread_id_clone,
443- message : e,
444- error_type : "inference" . to_string ( ) ,
445- round : None ,
446- } ,
447- ) ;
437+ // chat_send_inner already emits chat:error for known error paths.
438+ // Only log here to avoid duplicate error events.
439+ log:: error!( "[chat] chat_send_inner failed: {e}" ) ;
448440 }
449441 } ) ;
450442
@@ -501,15 +493,9 @@ pub async fn chat_send(
501493 chat_state_arc. remove ( & thread_id_clone) ;
502494
503495 if let Err ( e) = result {
504- let _ = app_clone. emit (
505- "chat:error" ,
506- ChatErrorEvent {
507- thread_id : thread_id_clone,
508- message : e,
509- error_type : "inference" . to_string ( ) ,
510- round : None ,
511- } ,
512- ) ;
496+ // chat_send_mobile already emits chat:error for known error paths.
497+ // Only log here to avoid duplicate error events.
498+ log:: error!( "[chat] chat_send_mobile failed: {e}" ) ;
513499 }
514500 } ) ;
515501
@@ -546,7 +532,19 @@ async fn chat_send_inner(
546532 let client = reqwest:: Client :: builder ( )
547533 . use_rustls_tls ( )
548534 . build ( )
549- . map_err ( |e| format ! ( "Failed to build HTTP client: {}" , e) ) ?;
535+ . map_err ( |e| {
536+ let msg = format ! ( "Failed to build HTTP client: {}" , e) ;
537+ let _ = app. emit (
538+ "chat:error" ,
539+ ChatErrorEvent {
540+ thread_id : thread_id. to_string ( ) ,
541+ message : msg. clone ( ) ,
542+ error_type : "network" . to_string ( ) ,
543+ round : None ,
544+ } ,
545+ ) ;
546+ msg
547+ } ) ?;
550548
551549 // ── Step 1: Load AI context ─────────────────────────────────────────
552550 let openclaw_context = load_openclaw_context ( app) ;
@@ -583,7 +581,11 @@ async fn chat_send_inner(
583581 . map ( |( skill_id, _) | skill_id)
584582 . collect ( ) ;
585583
586- log:: info!( "[chat] Recalling skill contexts for {} skill(s): {:?}" , skill_ids. len( ) , skill_ids) ;
584+ log:: info!(
585+ "[chat] Recalling skill contexts for {} skill(s): {:?}" ,
586+ skill_ids. len( ) ,
587+ skill_ids
588+ ) ;
587589
588590 let mut skill_contexts: Vec < String > = Vec :: new ( ) ;
589591 for sid in & skill_ids {
@@ -932,21 +934,39 @@ async fn chat_send_inner(
932934 }
933935
934936 // Parse the completion response
935- let completion: ChatCompletionResponse = response
936- . json ( )
937- . await
938- . map_err ( |e| format ! ( "Failed to parse inference response: {}" , e) ) ?;
937+ let completion: ChatCompletionResponse = response. json ( ) . await . map_err ( |e| {
938+ let msg = format ! ( "Failed to parse inference response: {}" , e) ;
939+ let _ = app. emit (
940+ "chat:error" ,
941+ ChatErrorEvent {
942+ thread_id : thread_id. to_string ( ) ,
943+ message : msg. clone ( ) ,
944+ error_type : "inference" . to_string ( ) ,
945+ round : Some ( round) ,
946+ } ,
947+ ) ;
948+ msg
949+ } ) ?;
939950
940951 // Accumulate token usage
941952 if let Some ( ref usage) = completion. usage {
942953 total_input_tokens += usage. prompt_tokens ;
943954 total_output_tokens += usage. completion_tokens ;
944955 }
945956
946- let choice = completion
947- . choices
948- . first ( )
949- . ok_or_else ( || "No choices in inference response" . to_string ( ) ) ?;
957+ let choice = completion. choices . first ( ) . ok_or_else ( || {
958+ let msg = "No choices in inference response" . to_string ( ) ;
959+ let _ = app. emit (
960+ "chat:error" ,
961+ ChatErrorEvent {
962+ thread_id : thread_id. to_string ( ) ,
963+ message : msg. clone ( ) ,
964+ error_type : "inference" . to_string ( ) ,
965+ round : Some ( round) ,
966+ } ,
967+ ) ;
968+ msg
969+ } ) ?;
950970
951971 log:: info!(
952972 "[chat] Round {} — finish_reason={:?}, tool_calls={}" ,
@@ -989,9 +1009,8 @@ async fn chat_send_inner(
9891009
9901010 let ( skill_id, tool_name) = parse_tool_name ( & tc. function . name ) ;
9911011
992- let args_value: serde_json:: Value =
993- serde_json:: from_str ( & tc. function . arguments )
994- . unwrap_or_else ( |_| serde_json:: Value :: Object ( serde_json:: Map :: new ( ) ) ) ;
1012+ let args_value: serde_json:: Value = serde_json:: from_str ( & tc. function . arguments )
1013+ . unwrap_or_else ( |_| serde_json:: Value :: Object ( serde_json:: Map :: new ( ) ) ) ;
9951014
9961015 // Emit tool_call event before executing
9971016 let _ = app. emit (
@@ -1056,9 +1075,7 @@ async fn chat_send_inner(
10561075 . content
10571076 . iter ( )
10581077 . filter_map ( |c| match c {
1059- crate :: runtime:: types:: ToolContent :: Text { text } => {
1060- Some ( text. as_str ( ) )
1061- }
1078+ crate :: runtime:: types:: ToolContent :: Text { text } => Some ( text. as_str ( ) ) ,
10621079 crate :: runtime:: types:: ToolContent :: Json { .. } => None ,
10631080 } )
10641081 . collect :: < Vec < & str > > ( )
@@ -1067,9 +1084,7 @@ async fn chat_send_inner(
10671084 // Check for JSON error pattern (matching TS behaviour)
10681085 let ( final_tool_str, final_success) = if !tool_result. is_error {
10691086 if let Ok ( parsed) = serde_json:: from_str :: < serde_json:: Value > ( & tool_content) {
1070- if let Some ( error_str) =
1071- parsed. get ( "error" ) . and_then ( |e| e. as_str ( ) )
1072- {
1087+ if let Some ( error_str) = parsed. get ( "error" ) . and_then ( |e| e. as_str ( ) ) {
10731088 ( format ! ( "Error: {}" , error_str) , false )
10741089 } else {
10751090 ( tool_content. clone ( ) , true )
@@ -1162,7 +1177,19 @@ async fn chat_send_mobile(
11621177 let client = reqwest:: Client :: builder ( )
11631178 . use_rustls_tls ( )
11641179 . build ( )
1165- . map_err ( |e| format ! ( "Failed to build HTTP client: {}" , e) ) ?;
1180+ . map_err ( |e| {
1181+ let msg = format ! ( "Failed to build HTTP client: {}" , e) ;
1182+ let _ = app. emit (
1183+ "chat:error" ,
1184+ ChatErrorEvent {
1185+ thread_id : thread_id. to_string ( ) ,
1186+ message : msg. clone ( ) ,
1187+ error_type : "network" . to_string ( ) ,
1188+ round : None ,
1189+ } ,
1190+ ) ;
1191+ msg
1192+ } ) ?;
11661193
11671194 // ── Step 1: Load AI context ─────────────────────────────────────────
11681195 let openclaw_context = load_openclaw_context ( app) ;
@@ -1307,20 +1334,38 @@ async fn chat_send_mobile(
13071334 return Err ( msg) ;
13081335 }
13091336
1310- let completion: ChatCompletionResponse = response
1311- . json ( )
1312- . await
1313- . map_err ( |e| format ! ( "Failed to parse inference response: {}" , e) ) ?;
1337+ let completion: ChatCompletionResponse = response. json ( ) . await . map_err ( |e| {
1338+ let msg = format ! ( "Failed to parse inference response: {}" , e) ;
1339+ let _ = app. emit (
1340+ "chat:error" ,
1341+ ChatErrorEvent {
1342+ thread_id : thread_id. to_string ( ) ,
1343+ message : msg. clone ( ) ,
1344+ error_type : "inference" . to_string ( ) ,
1345+ round : None ,
1346+ } ,
1347+ ) ;
1348+ msg
1349+ } ) ?;
13141350
13151351 let ( total_input_tokens, total_output_tokens) = completion
13161352 . usage
13171353 . map ( |u| ( u. prompt_tokens , u. completion_tokens ) )
13181354 . unwrap_or ( ( 0 , 0 ) ) ;
13191355
1320- let choice = completion
1321- . choices
1322- . first ( )
1323- . ok_or_else ( || "No choices in inference response" . to_string ( ) ) ?;
1356+ let choice = completion. choices . first ( ) . ok_or_else ( || {
1357+ let msg = "No choices in inference response" . to_string ( ) ;
1358+ let _ = app. emit (
1359+ "chat:error" ,
1360+ ChatErrorEvent {
1361+ thread_id : thread_id. to_string ( ) ,
1362+ message : msg. clone ( ) ,
1363+ error_type : "inference" . to_string ( ) ,
1364+ round : None ,
1365+ } ,
1366+ ) ;
1367+ msg
1368+ } ) ?;
13241369
13251370 let full_response = choice. message . content . clone ( ) . unwrap_or_default ( ) ;
13261371
0 commit comments