diff --git a/crates/mind-body/src/self_run.rs b/crates/mind-body/src/self_run.rs index e064d3d..e4bc9a4 100644 --- a/crates/mind-body/src/self_run.rs +++ b/crates/mind-body/src/self_run.rs @@ -131,16 +131,10 @@ fn task_section(mind: &dyn MindPort, claims: &[PipelineRef]) -> String { _ => task.push_str(&format!("\n\nThe run claims `{}`, which could not be read as a follow-up: its task is unknown.", claim.id.0)), } } - if task.len() > TASK_LIMIT { - let mut end = TASK_LIMIT; - while !task.is_char_boundary(end) { - end -= 1; - } - task.truncate(end); - task.push_str("\n[task truncated]"); - } - task + cut_at(task, TASK_LIMIT) } +/// `text` cut at a character boundary to `limit` bytes, marked when cut.fn cut_at(mut text: String, limit: usize) -> String { if text.len() > limit { let mut end = limit; while !text.is_char_boundary(end) { end -= 1; } text.truncate(end); text.push_str(" +[task truncated]"); } text} /// What the child is told once, at the context ceiling. pub const FLUSH_MESSAGE: &str = "The context ceiling is reached. Admit to the mind what it lacks, close your run, and end."; diff --git a/crates/mind-body/src/self_run/tests.rs b/crates/mind-body/src/self_run/tests.rs index c9b96c1..3c6b6fe 100644 --- a/crates/mind-body/src/self_run/tests.rs +++ b/crates/mind-body/src/self_run/tests.rs @@ -1385,3 +1385,12 @@ fn a_run_without_claims_gets_the_identity_lines_alone() { let ran = go(vec![Out(result_line())]); assert_eq!(ran.written()[0], start_message(&Slug("yggdrasil".into()), &run_ref("selfrun-a"))); } + +#[test] +fn the_cut_keeps_exactly_the_limit_whole_characters_and_marks_only_a_real_cut() { + assert_eq!(cut_at("abcd".into(), 4), "abcd"); + assert_eq!(cut_at("abcde".into(), 4), "abcd\n[task truncated]"); + // "\u{e9}" is two bytes: a limit inside one keeps the characters before it. + assert_eq!(cut_at("a\u{e9}\u{e9}".into(), 4), "a\u{e9}\n[task truncated]"); + assert_eq!(cut_at("a\u{e9}\u{e9}".into(), 5), "a\u{e9}\u{e9}"); +}