Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 36 additions & 5 deletions crates/oxide-code/src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,15 @@ pub(crate) async fn auto_compact_if_needed(

sink.emit(Event::AutoCompactionStarted, "auto-compaction-started");
let summary = match await_unless_aborted(
compaction::compact_session(client, auto.prompt, auto.tools, messages, None, auto.spent),
compaction::compact_session(
client,
auto.prompt,
auto.tools,
messages,
None,
sink,
auto.spent,
),
ops,
pending,
)
Expand Down Expand Up @@ -1612,10 +1620,18 @@ mod tests {
assert!(!compacted);
assert_eq!(failures, 1);
assert!(matches!(&messages[0].content[0], ContentBlock::Text { text } if text == "hi"));
let events = sink.events();
assert!(
sink.events()
.iter()
.any(|event| matches!(event, Event::Error(message) if message.contains("Session write failed")))
matches!(
events.as_slice(),
[
Event::AutoCompactionStarted,
Event::SummaryToken(summary),
Event::SummaryDiscarded,
Event::Error(message),
] if summary == "auto summary" && message.starts_with("Session write failed")
),
"the unsaved summary is discarded before the write failure: {events:?}",
);
}

Expand Down Expand Up @@ -1786,6 +1802,7 @@ mod tests {
),
text_turn("auto summary"),
]);
let sink = CapturingSink::new();
let tracker = FileTracker::default();
let mut messages = vec![
Message::user("hi"),
Expand All @@ -1799,7 +1816,7 @@ mod tests {
&client,
&session,
&mut messages,
&CapturingSink::new(),
&sink,
&mut inert_ops(),
&mut Vec::new(),
Some(&mut AutoCompact {
Expand Down Expand Up @@ -1852,6 +1869,20 @@ mod tests {
assert!(
matches!(&messages[0].content[0], ContentBlock::Text { text } if text.contains("auto summary"))
);
let events = sink.events();
assert!(
matches!(
events.as_slice(),
[
Event::AutoCompactionStarted,
Event::SummaryToken(overflowed),
Event::SummaryDiscarded,
Event::SummaryToken(retried),
Event::SessionCompacted { automatic: true, .. },
] if overflowed == "partial" && retried == "auto summary"
),
"the overflowed summary is discarded before the retry streams: {events:?}",
);
}

// ── agent_turn ──
Expand Down
18 changes: 12 additions & 6 deletions crates/oxide-code/src/agent/compact_boundary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use crate::message::Message;
use crate::protocol::{Event, EventSink};
use crate::session::handle::SessionHandle;

/// Persists a compact boundary and swaps the live transcript to the synthetic summary root.
/// Persists a compact boundary and swaps the live transcript to the synthetic summary root. A
/// failed write discards the streamed summary and leaves the transcript unchanged.
pub(crate) async fn replace_session_with_summary(
session: &SessionHandle,
file_tracker: &FileTracker,
Expand All @@ -20,8 +21,9 @@ pub(crate) async fn replace_session_with_summary(
let outcome = session
.compact(summary.clone(), instructions.clone(), synthetic.clone())
.await;
sink.session_write_error(outcome.failure.as_deref());
if outcome.failure.is_some() {
if let Some(failure) = outcome.failure.as_deref() {
sink.emit(Event::SummaryDiscarded, "summary-discarded");
sink.session_write_error(Some(failure));
return false;
}

Expand Down Expand Up @@ -120,10 +122,14 @@ mod tests {
matches!(&messages[3].content[0], ContentBlock::Text { text } if text == "found it")
);
assert_eq!(tracker.snapshot_all().len(), 1);
let events = sink.events();
assert!(
sink.events()
.iter()
.any(|event| matches!(event, Event::Error(message) if message.contains("Session write failed")))
matches!(
events.as_slice(),
[Event::SummaryDiscarded, Event::Error(message)]
if message.contains("Session write failed")
),
"the unsaved summary is discarded before the write error: {events:?}",
);
}

Expand Down
Loading
Loading