diff --git a/src/agent/api/mod.rs b/src/agent/api/mod.rs index e448e2b..a3c73a0 100644 --- a/src/agent/api/mod.rs +++ b/src/agent/api/mod.rs @@ -245,7 +245,7 @@ pub(crate) async fn send_and_check( status, elapsed.as_secs_f64(), url, - &body[..body.len().min(500)] + &body[..body.floor_char_boundary(body.len().min(500))] ); if let Some(json) = request_json { let log_dir = dirs::home_dir() @@ -260,7 +260,7 @@ pub(crate) async fn send_and_check( ); } } - anyhow::bail!("HTTP {} ({}): {}", status, url, &body[..body.len().min(1000)]); + anyhow::bail!("HTTP {} ({}): {}", status, url, &body[..body.floor_char_boundary(body.len().min(1000))]); } if debug { diff --git a/src/agent/context.rs b/src/agent/context.rs index 17ebba0..e550885 100644 --- a/src/agent/context.rs +++ b/src/agent/context.rs @@ -507,7 +507,8 @@ impl ResponseParser { if let Some(ref mut f) = log_file { use std::io::Write; for c in &calls { - let _ = writeln!(f, "tool_call: {} args={}", c.name, &c.arguments[..c.arguments.len().min(200)]); + let end = c.arguments.floor_char_boundary(c.arguments.len().min(200)); + let _ = writeln!(f, "tool_call: {} args={}", c.name, &c.arguments[..end]); } } } @@ -523,12 +524,13 @@ impl ResponseParser { let _ = writeln!(f, "done: {} chars, {} tags, ctx: {} tokens", full_text.len(), tc_count, ctx_tokens); if tc_count == 0 && full_text.len() > 0 { - let _ = writeln!(f, "full text:\n{}", &full_text[..full_text.len().min(2000)]); + let end = full_text.floor_char_boundary(full_text.len().min(2000)); + let _ = writeln!(f, "full text:\n{}", &full_text[..end]); } for (i, part) in full_text.split("").enumerate() { if i > 0 { - let _ = writeln!(f, "tool_call body: {}...", - &part[..part.len().min(200)]); + let end = part.floor_char_boundary(part.len().min(200)); + let _ = writeln!(f, "tool_call body: {}...", &part[..end]); } } } diff --git a/src/agent/oneshot.rs b/src/agent/oneshot.rs index eeaa057..9c31307 100644 --- a/src/agent/oneshot.rs +++ b/src/agent/oneshot.rs @@ -179,7 +179,7 @@ impl AutoAgent { } dbglog!("[auto] {} response: {}", - self.name, &text[..text.len().min(200)]); + self.name, &text[..text.floor_char_boundary(text.len().min(200))]); if next_step < self.steps.len() { if let Some(ref check) = bail_fn { diff --git a/src/claude/memory-search.rs b/src/claude/memory-search.rs index 32b9f5c..704b8b4 100644 --- a/src/claude/memory-search.rs +++ b/src/claude/memory-search.rs @@ -102,7 +102,7 @@ fn run_agent_and_parse(agent: &str, session_arg: &Option) { std::process::exit(1); } - eprintln!("Running {} agent (session {})...", agent, &session_id[..8.min(session_id.len())]); + eprintln!("Running {} agent (session {})...", agent, &session_id[..session_id.floor_char_boundary(8.min(session_id.len()))]); let output = Command::new("poc-memory") .args(["agent", "run", agent, "--count", "1", "--local"]) diff --git a/src/cli/agent.rs b/src/cli/agent.rs index 78fa6e8..652aa13 100644 --- a/src/cli/agent.rs +++ b/src/cli/agent.rs @@ -136,7 +136,7 @@ pub fn cmd_digest_links(do_apply: bool) -> Result<(), String> { for (i, link) in links.iter().enumerate() { println!(" {:3}. {} → {}", i + 1, link.source, link.target); if !link.reason.is_empty() { - println!(" ({})", &link.reason[..link.reason.len().min(80)]); + println!(" ({})", &link.reason[..link.reason.floor_char_boundary(link.reason.len().min(80))]); } } println!("\nTo apply: poc-memory digest-links --apply"); diff --git a/src/subconscious/defs.rs b/src/subconscious/defs.rs index 0929bec..9d2b136 100644 --- a/src/subconscious/defs.rs +++ b/src/subconscious/defs.rs @@ -606,7 +606,7 @@ fn resolve_conversation(budget: Option) -> String { if total_bytes >= max_bytes { break; } let name = if role == "user" { &cfg.user_name } else { &cfg.assistant_name }; let formatted = if !ts.is_empty() { - oldest_ts = ts[..ts.len().min(19)].to_string(); + oldest_ts = ts[..ts.floor_char_boundary(ts.len().min(19))].to_string(); format!("**{}** {}: {}", name, &oldest_ts, content) } else { format!("**{}:** {}", name, content)