ai: fix UTF-8 byte-slice panics in insight_generator log/truncation paths

Switch four `&s[..N]` / `&s[..s.len().min(N)]` sites to
`chars().take(N).collect::<String>()` so truncation lands on character
boundaries instead of mid-codepoint. The agentic summary preview log
was panicking when generated content hit an em-dash at byte 200; the
few-shot passage cap, brief_json_args debug formatter, and a test
assertion message had the same latent bug.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cameron
2026-05-15 15:10:02 -04:00
parent 8503ef7884
commit c30cadde02

View File

@@ -1749,8 +1749,8 @@ Return ONLY the summary, nothing else."#,
.iter()
.enumerate()
.map(|(i, c)| {
let trimmed = if c.len() > 1000 {
format!("{}", &c[..1000])
let trimmed = if c.chars().count() > 1000 {
format!("{}", c.chars().take(1000).collect::<String>())
} else {
c.clone()
};
@@ -3406,8 +3406,8 @@ Return ONLY the summary, nothing else."#,
obj.iter()
.map(|(k, v)| {
let rendered = match v {
serde_json::Value::String(s) if s.len() > 40 => {
format!("\"{}...\"", &s[..40])
serde_json::Value::String(s) if s.chars().count() > 40 => {
format!("\"{}...\"", s.chars().take(40).collect::<String>())
}
_ => v.to_string(),
};
@@ -4088,10 +4088,11 @@ Return ONLY the summary, nothing else."#,
let title = title_raw.trim().trim_matches('"').to_string();
log::info!("Agentic generated title: {}", title);
let summary_preview: String = final_content.chars().take(200).collect();
log::info!(
"Agentic generated summary ({} chars): {}",
final_content.len(),
&final_content[..final_content.len().min(200)]
summary_preview
);
// 14. Serialize the full message history for training data
@@ -4671,7 +4672,7 @@ mod tests {
assert!(
out.starts_with("You are a journal writer in first person, warm and reflective."),
"custom prompt must lead the system content; got: {}",
&out[..out.len().min(200)],
out.chars().take(200).collect::<String>(),
);
assert!(
!out.contains("personal photo memory assistant"),