diff --git a/src/apps/cli/src/ui/login_form.rs b/src/apps/cli/src/ui/login_form.rs index d1520df88c..07abe834ec 100644 --- a/src/apps/cli/src/ui/login_form.rs +++ b/src/apps/cli/src/ui/login_form.rs @@ -411,7 +411,7 @@ fn truncate_id(id: &str) -> String { if id.len() <= 8 { id.to_string() } else { - format!("{}…", &id[..8]) + format!("{}…", id.chars().take(8).collect::()) } } @@ -419,6 +419,12 @@ fn truncate_id(id: &str) -> String { mod tests { use super::*; use crossterm::event::KeyModifiers; + + #[test] + fn truncate_id_handles_multibyte_ids() { + assert_eq!(truncate_id(&"中".repeat(9)), format!("{}…", "中".repeat(8))); + } + #[test] fn enter_starts_or_checks_the_host_owned_authorization() { let mut form = LoginFormState::new(); diff --git a/src/crates/assembly/core/src/agentic/tools/pipeline/tool_pipeline.rs b/src/crates/assembly/core/src/agentic/tools/pipeline/tool_pipeline.rs index 297686ebcc..a0de4b992d 100644 --- a/src/crates/assembly/core/src/agentic/tools/pipeline/tool_pipeline.rs +++ b/src/crates/assembly/core/src/agentic/tools/pipeline/tool_pipeline.rs @@ -16,8 +16,8 @@ use crate::agentic::tools::tool_result_storage; #[cfg(feature = "opencode-plugin-host")] use crate::agentic::WorkspaceBinding; use crate::native_hooks::{self, NativeHookSessionFacts}; -use crate::util::elapsed_ms_u64; use crate::util::errors::{OpenBitFunError, OpenBitFunResult}; +use crate::util::{elapsed_ms_u64, truncate_at_char_boundary}; use futures::future::join_all; use log::{debug, error, info, warn}; use openbitfun_agent_runtime::permission::{ @@ -735,7 +735,8 @@ fn approval_resource_summary(resource: &str) -> String { } } if summary.len() > 160 { - summary.truncate(160); + let boundary = truncate_at_char_boundary(&summary, 160).len(); + summary.truncate(boundary); summary.push('…'); } summary @@ -3766,6 +3767,14 @@ mod tests { use std::time::SystemTime; use tokio::time::{sleep, Duration}; + #[test] + fn approval_resource_summary_truncates_multibyte_text_safely() { + let summary = approval_resource_summary(&"中".repeat(54)); + + assert_eq!(summary, format!("{}…", "中".repeat(53))); + assert!(summary.is_char_boundary(summary.len())); + } + fn loaded_spec(tool_name: &str, catalog_generation: u64) -> LoadedDeferredToolSpec { LoadedDeferredToolSpec { tool_name: tool_name.to_string(), diff --git a/src/crates/assembly/core/src/service_agent_runtime.rs b/src/crates/assembly/core/src/service_agent_runtime.rs index a5b757af57..798041c8a1 100644 --- a/src/crates/assembly/core/src/service_agent_runtime.rs +++ b/src/crates/assembly/core/src/service_agent_runtime.rs @@ -3340,7 +3340,7 @@ impl RemoteDialogRuntimeHost for CoreRemoteDialogRuntimeHost<'_> { return; } let workspace = binding_workspace_for_terminal; - let name = format!("Chat-{}", &sid[..8.min(sid.len())]); + let name = format!("Chat-{}", sid.chars().take(8).collect::()); match binding .get_or_create( &sid, diff --git a/src/crates/services/terminal/src/session/binding.rs b/src/crates/services/terminal/src/session/binding.rs index 98fe039f23..25af4e9827 100644 --- a/src/crates/services/terminal/src/session/binding.rs +++ b/src/crates/services/terminal/src/session/binding.rs @@ -100,14 +100,14 @@ impl TerminalSessionBinding { let terminal_session_id = options.session_id.unwrap_or_else(|| { format!( "term-{}-{}", - &owner_id[..8.min(owner_id.len())], + owner_id.chars().take(8).collect::(), &uuid::Uuid::new_v4().to_string()[..8] ) }); - let session_name = options - .session_name - .unwrap_or_else(|| format!("Terminal-{}", &owner_id[..8.min(owner_id.len())])); + let session_name = options.session_name.unwrap_or_else(|| { + format!("Terminal-{}", owner_id.chars().take(8).collect::()) + }); // Create the session let _session = session_manager @@ -185,14 +185,17 @@ impl TerminalSessionBinding { let session_id = options.session_id.unwrap_or_else(|| { format!( "bg-{}-{}", - &owner_id[..8.min(owner_id.len())], + owner_id.chars().take(8).collect::(), &uuid::Uuid::new_v4().to_string()[..8] ) }); - let session_name = options - .session_name - .unwrap_or_else(|| format!("Background-{}", &session_id[..8.min(session_id.len())])); + let session_name = options.session_name.unwrap_or_else(|| { + format!( + "Background-{}", + session_id.chars().take(8).collect::() + ) + }); let _session = session_manager .create_session( diff --git a/src/crates/services/terminal/src/session/manager.rs b/src/crates/services/terminal/src/session/manager.rs index 29d83c963c..8228298664 100644 --- a/src/crates/services/terminal/src/session/manager.rs +++ b/src/crates/services/terminal/src/session/manager.rs @@ -690,7 +690,12 @@ impl SessionManager { }); // Generate name - let name = name.unwrap_or_else(|| format!("Terminal {}", &session_id[..8])); + let name = name.unwrap_or_else(|| { + format!( + "Terminal {}", + session_id.chars().take(8).collect::() + ) + }); // Generate nonce for shell integration let nonce = uuid::Uuid::new_v4().to_string();