diff --git a/CHANGELOG.md b/CHANGELOG.md index a402dc1..eb8d787 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ `sessions read` showed one entry with its tool output cut at 2,000 characters, not at 40,000 as with `--count 1`. `read_session` now takes a `count` of 0 as 1. +- **Handoff:** "Where it stopped" says which entries after the agent's + last message it had no room for when none of them fit. It said nothing + of them. The entries it says it left out for room no longer start or + end with a system note, which a handoff always leaves out, so a note + alone is no longer said to be left out for room. - **`status`, `check_limits` and `doctor`** say why an account can't be read in the same words. `check_limits`' verdict on the first account says how to fix it, as the others do, not only that its limits can't be diff --git a/src/sessions/handoff.rs b/src/sessions/handoff.rs index 84067e0..f2c75ea 100644 --- a/src/sessions/handoff.rs +++ b/src/sessions/handoff.rs @@ -466,7 +466,7 @@ fn stopped(entries: &[Entry], stop: usize, quoted: &[usize], share: usize, now: ..SAID }; let first = render(stop, last, &opening, now); - let (after, _) = fit( + let (after, full) = fit( stop + 1..entries.len(), share.saturating_sub(first.len()), |at| { @@ -483,11 +483,13 @@ fn stopped(entries: &[Entry], stop: usize, quoted: &[usize], share: usize, now: }) }, ); - let from = after.first().map_or(entries.len(), |(at, _)| *at); - let gap = match from > stop + 1 && from < entries.len() { - true => format!("\n\n{}", left_out(stop + 1, from - 1)), - false => String::new(), - }; + // A system note is left out wherever it falls, not for room. + let gap = full + .and_then(|at| { + let from = (stop + 1..=at).find(|&next| entries[next].kind != Kind::System)?; + Some(format!("\n\n{}", left_out(from, at))) + }) + .unwrap_or_default(); let what = match (last.kind, entries.len() - stop - 1) { (Kind::Assistant, 0) => "Its last message, the session's last entry.".to_owned(), (Kind::Assistant, count) => format!( @@ -995,6 +997,57 @@ mod tests { ); } + /// Where it stopped says which entries after the last message it had no + /// room for: from the first after it that isn't a system note to the one + /// that didn't fit. + #[test] + fn where_it_stopped_says_just_what_it_left_out_for_room() { + let failed = "test quoted_commas ... FAILED\n"; + let tests = |output: &str| { + call( + "Bash", + r#"{"command": "cargo test"}"#, + output, + true, + Role::Work, + ) + }; + let stop = |after: Vec| { + let mut entries = vec![ + said(Kind::User, 0, "Fix the CSV parser."), + said(Kind::Assistant, 1, "Running the tests."), + ]; + entries.extend(after); + stopped(&entries, 1, &[], 1000, 10 * MINUTE) + }; + // A system note is left out wherever it falls, not for room. + let noted = stop(vec![said(Kind::System, 2, "A reminder."), tests(failed)]); + assert!(!noted.contains("left out for room"), "{noted}"); + // Room ran out at #2; the note after it doesn't count with it. + let cut = stop(vec![ + tests(&failed.repeat(60)), + said(Kind::System, 3, "A reminder."), + tests(failed), + ]); + assert!(cut.contains("\nEntry #2 is left out for room.\n"), "{cut}"); + // Nor does the note before it. + let noted_first = stop(vec![ + said(Kind::System, 2, "A reminder."), + tests(&failed.repeat(60)), + tests(failed), + ]); + assert!( + noted_first.contains("\nEntry #3 is left out for room.\n"), + "{noted_first}" + ); + // With room for none of the entries after the last message. + let none = stop(vec![tests(&failed.repeat(60))]); + assert!( + none.contains("\nEntry #2 is left out for room.\n"), + "{none}" + ); + } + #[test] fn a_request_answered_is_kept_though_the_next_starts_the_same() { let first = "Run the tests and fix the failures in the parser crate.";