feat: page console history across compaction

This commit is contained in:
2026-09-30 08:13:07 +09:00
parent cafbb73a22
commit 34ed988b75
38 changed files with 3107 additions and 144 deletions
+6 -3
View File
@@ -619,8 +619,8 @@ mod tests {
let (committed_tx, committed_rx) = mpsc::channel();
let commit_thread = thread::spawn(move || {
// This mirrors Worker::append_entry ordering: clear in-flight first,
// then publish the finalized AssistantItem. AssistantItem entries
// are mirror-only and are not delivered as live entry events.
// then publish the finalized AssistantItem. Identity-aware clients
// reconcile this durable commit with the preceding streamed block.
in_flight_for_commit.clear_for_committed_item_then(&assistant_item, || {
sink_for_commit.publish(assistant_entry);
});
@@ -639,7 +639,10 @@ mod tests {
in_flight_snapshot.blocks.as_slice(),
[InFlightBlock::Text { text, finished: true }] if text == "done"
));
assert!(entry_rx.try_recv().is_err());
assert!(matches!(
entry_rx.try_recv(),
Ok(LogEntry::AnnotatedAssistantItem { .. })
));
let post_commit_guard = in_flight.snapshot_guard();
assert!(snapshot_from_guard(&post_commit_guard).is_empty());
}
+38 -2
View File
@@ -34,7 +34,41 @@ pub fn live_log_entry_event(entry: LogEntry) -> Option<Event> {
session_store::public_snapshot::project_current_session_snapshot(&[entry]);
Some(Event::SegmentRotated { session })
}
LogEntry::AnnotatedUserInput { segments, .. } => Some(Event::UserMessage { segments }),
LogEntry::AnnotatedUserInput {
ts,
segments,
history,
extensions,
} => {
let projected = session_store::public_snapshot::project_current_session_snapshot(&[
LogEntry::AnnotatedUserInput {
ts,
segments: segments.clone(),
history,
extensions,
},
]);
let entry_id = projected
.entries
.iter()
.find(|entry| {
matches!(
entry.data,
protocol::SessionSnapshotEntryData::UserInput { .. }
)
})
.map(|entry| entry.entry_id.clone());
Some(Event::UserMessage { entry_id, segments })
}
entry
@ (LogEntry::AnnotatedAssistantItem { .. } | LogEntry::AnnotatedToolResult { .. }) => {
let mut projected =
session_store::public_snapshot::project_current_session_snapshot(&[entry]);
projected
.entries
.pop()
.map(|entry| Event::SessionEntryCommitted { entry })
}
LogEntry::AnnotatedSystemItem { entry, .. } => {
let value = serde_json::to_value(&entry.item).expect("SystemItem is Serialize");
Some(Event::SystemItem { item: value })
@@ -94,7 +128,9 @@ mod tests {
.expect("UserInput must be live-relevant");
match event {
Event::UserMessage { segments: echoed } => assert_eq!(echoed, segments),
Event::UserMessage {
segments: echoed, ..
} => assert_eq!(echoed, segments),
other => panic!("expected UserMessage, got {other:?}"),
}
}
+6 -7
View File
@@ -92,14 +92,11 @@ impl SegmentLogSink {
/// - `LogEntry::AnnotatedSegmentStart` → `Event::SegmentRotated` on the wire.
/// - `LogEntry::AnnotatedUserInput` → `Event::UserMessage`.
/// - `LogEntry::AnnotatedSystemItem` → `Event::SystemItem`.
/// - assistant/tool commits → `Event::SessionEntryCommitted`.
/// - `LogEntry::Invoke` → `Event::InvokeStart`.
/// Everything else (AssistantItem, ToolResult, TurnEnd,
/// RunCompleted, RunErrored, PausedTurnAbandoned, LlmUsage, Extension,
/// ConfigChanged) is reflected in the mirror so reconnect snapshots stay accurate,
/// but is not sent live — the streaming events (TextDelta /
/// ToolCallStart / ToolResult / TurnEnd / etc.) already provide
/// that data, and re-broadcasting it as a typed entry would just
/// double-render every block on the client side.
/// Operational checkpoints remain mirror-only. Assistant/tool streaming
/// events are still the in-flight authority; the committed-entry event adds
/// the durable stable identity needed to reconcile retained history.
pub fn publish(&self, entry: LogEntry) {
let mut mirror = self
.inner
@@ -122,6 +119,8 @@ impl SegmentLogSink {
entry,
LogEntry::AnnotatedSegmentStart { .. }
| LogEntry::AnnotatedUserInput { .. }
| LogEntry::AnnotatedAssistantItem { .. }
| LogEntry::AnnotatedToolResult { .. }
| LogEntry::AnnotatedSystemItem { .. }
| LogEntry::Invoke { .. }
)