Skip to content

Commit cd2b292

Browse files
authored
Merge pull request tinyhumansai#38 from graycyrus/develop
fixes: conversation fixes
2 parents 6d6be24 + 5e29a5d commit cd2b292

95 files changed

Lines changed: 1721 additions & 1149 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,12 @@ Key updates from recent commits (cd9ebcd to current):
471471

472472
## Key Patterns
473473

474+
- **MANDATORY: Pre-completion checks**: Before considering ANY task complete, ALWAYS run these checks and fix all errors:
475+
1. `npx prettier --check .` (formatting)
476+
2. `npx eslint .` (lint)
477+
3. `npx tsc --noEmit` (TypeScript)
478+
4. `cargo fmt --manifest-path src-tauri/Cargo.toml` (Rust formatting, if Rust files were changed)
479+
5. `cargo check --manifest-path src-tauri/Cargo.toml` (Rust compilation, if Rust files were changed)
474480
- **Code Quality**: ESLint and Prettier enforce code standards with Husky hooks. Use type-only imports (`import type`) and consolidate imports from same modules.
475481
- **No dynamic imports**: All imports must be static `import` statements at the top of the file. Do not use `await import()` or `import().then()` inside functions or code blocks. Use try/catch around Tauri API calls for non-Tauri environments instead.
476482
- **No localStorage**: Avoid `localStorage` and `sessionStorage`; use Redux (and persist) for app state. Remove any direct usage when working on affected code.

src-tauri/src/ai/encryption.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
//! encrypted at rest using AES-256-GCM. Keys are derived from a user
55
//! password via Argon2id.
66
7+
use aes_gcm::aead::rand_core::RngCore;
78
use aes_gcm::{
89
aead::{Aead, KeyInit, OsRng},
910
Aes256Gcm, Nonce,
1011
};
1112
use argon2::{self, Algorithm, Argon2, Params, Version};
12-
use aes_gcm::aead::rand_core::RngCore;
1313
use serde::{Deserialize, Serialize};
1414
use std::path::PathBuf;
1515

src-tauri/src/bin/openhuman-cli.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,10 @@ enum Command {
8080
},
8181

8282
/// Encrypt a secret
83-
Encrypt {
84-
plaintext: String,
85-
},
83+
Encrypt { plaintext: String },
8684

8785
/// Decrypt a secret
88-
Decrypt {
89-
ciphertext: String,
90-
},
86+
Decrypt { ciphertext: String },
9187

9288
/// Toggle browser allow-all runtime flag
9389
BrowserAllowAll {
@@ -324,7 +320,9 @@ fn load_config() -> Result<openhuman::openhuman::config::Config, String> {
324320
.map_err(|e| format!("failed to load config: {e}"))
325321
}
326322

327-
fn status_value(status: openhuman::openhuman::service::ServiceStatus) -> Result<serde_json::Value, String> {
323+
fn status_value(
324+
status: openhuman::openhuman::service::ServiceStatus,
325+
) -> Result<serde_json::Value, String> {
328326
serde_json::to_value(status).map_err(|e| format!("failed to serialize service status: {e}"))
329327
}
330328

src-tauri/src/commands/auth.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use std::sync::Arc;
88
pub static SESSION_SERVICE: Lazy<Arc<SessionService>> =
99
Lazy::new(|| Arc::new(SessionService::new()));
1010

11-
1211
/// Get the current authentication state
1312
#[tauri::command]
1413
pub fn get_auth_state() -> AuthState {

src-tauri/src/commands/chat.rs

Lines changed: 94 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -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

src-tauri/src/commands/memory.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,10 @@ pub async fn init_memory_client(
8686
jwt_token: String,
8787
state: tauri::State<'_, MemoryState>,
8888
) -> Result<(), String> {
89-
log::info!("[memory] init_memory_client: entry (token_present={})", !jwt_token.trim().is_empty());
89+
log::info!(
90+
"[memory] init_memory_client: entry (token_present={})",
91+
!jwt_token.trim().is_empty()
92+
);
9093
let client = MemoryClient::from_token(jwt_token).map(Arc::new);
9194
if client.is_none() {
9295
log::warn!("[memory] init_memory_client: exit — empty token, memory layer disabled");
@@ -149,7 +152,10 @@ pub async fn memory_query(
149152
.query_skill_context(&skill_id, &integration_id, &query, max_chunks.unwrap_or(10))
150153
.await;
151154
match &result {
152-
Ok(ctx) => log::info!("[memory] memory_query: exit — ok (context_len={})", ctx.len()),
155+
Ok(ctx) => log::info!(
156+
"[memory] memory_query: exit — ok (context_len={})",
157+
ctx.len()
158+
),
153159
Err(e) => log::warn!("[memory] memory_query: exit — error: {e}"),
154160
}
155161
result
@@ -218,9 +224,10 @@ pub async fn memory_query_namespace(
218224
) -> Result<String, String> {
219225
let client = state.0.lock().map_err(|e| e.to_string())?.clone();
220226
match client {
221-
Some(c) => c
222-
.query_namespace_context(&namespace, &query, max_chunks.unwrap_or(10))
223-
.await,
227+
Some(c) => {
228+
c.query_namespace_context(&namespace, &query, max_chunks.unwrap_or(10))
229+
.await
230+
}
224231
None => Err("Memory layer not configured — JWT token not yet set".into()),
225232
}
226233
}
@@ -233,9 +240,10 @@ pub async fn memory_recall_namespace(
233240
) -> Result<Option<String>, String> {
234241
let client = state.0.lock().map_err(|e| e.to_string())?.clone();
235242
match client {
236-
Some(c) => c
237-
.recall_namespace_context(&namespace, max_chunks.unwrap_or(10))
238-
.await,
243+
Some(c) => {
244+
c.recall_namespace_context(&namespace, max_chunks.unwrap_or(10))
245+
.await
246+
}
239247
None => Err("Memory layer not configured — JWT token not yet set".into()),
240248
}
241249
}

src-tauri/src/commands/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ pub mod auth;
22
pub mod chat;
33
pub mod memory;
44
pub mod model;
5+
pub mod openhuman;
56
pub mod runtime;
67
pub mod socket;
7-
pub mod openhuman;
88
pub mod unified_skills;
99

1010
#[cfg(desktop)]
@@ -15,9 +15,9 @@ pub use auth::*;
1515
pub use chat::{chat_cancel, chat_send};
1616
pub use memory::*;
1717
pub use model::*;
18+
pub use openhuman::*;
1819
pub use runtime::*;
1920
pub use socket::*;
20-
pub use openhuman::*;
2121

2222
#[cfg(desktop)]
2323
pub use window::*;

0 commit comments

Comments
 (0)