mirror of
https://github.com/openai/codex.git
synced 2026-03-08 23:53:21 +00:00
Compare commits
6 Commits
dev/flaky-
...
tui-compac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a91722a94f | ||
|
|
4bad0ac715 | ||
|
|
c350201329 | ||
|
|
36c3330d9b | ||
|
|
67cb0d5b0e | ||
|
|
c27e15222f |
@@ -61,6 +61,8 @@ use codex_core::protocol::ExecCommandEndEvent;
|
||||
use codex_core::protocol::ExecCommandOutputDeltaEvent;
|
||||
use codex_core::protocol::ExecCommandSource;
|
||||
use codex_core::protocol::ExitedReviewModeEvent;
|
||||
use codex_core::protocol::ItemCompletedEvent;
|
||||
use codex_core::protocol::ItemStartedEvent;
|
||||
use codex_core::protocol::ListCustomPromptsResponseEvent;
|
||||
use codex_core::protocol::ListSkillsResponseEvent;
|
||||
use codex_core::protocol::McpListToolsResponseEvent;
|
||||
@@ -103,6 +105,8 @@ use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::Settings;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::items::ContextCompactionItem;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::models::local_image_label_text;
|
||||
use codex_protocol::parse_command::ParsedCommand;
|
||||
use codex_protocol::request_user_input::RequestUserInputEvent;
|
||||
@@ -3415,6 +3419,8 @@ impl ChatWidget {
|
||||
force_reload: true,
|
||||
});
|
||||
}
|
||||
EventMsg::ItemStarted(ev) => self.on_item_started(ev, from_replay),
|
||||
EventMsg::ItemCompleted(ev) => self.on_item_completed(ev, from_replay),
|
||||
EventMsg::ShutdownComplete => self.on_shutdown_complete(),
|
||||
EventMsg::TurnDiff(TurnDiffEvent { unified_diff }) => self.on_turn_diff(unified_diff),
|
||||
EventMsg::DeprecationNotice(ev) => self.on_deprecation_notice(ev),
|
||||
@@ -3437,7 +3443,7 @@ impl ChatWidget {
|
||||
self.on_entered_review_mode(review_request, from_replay)
|
||||
}
|
||||
EventMsg::ExitedReviewMode(review) => self.on_exited_review_mode(review),
|
||||
EventMsg::ContextCompacted(_) => self.on_agent_message("Context compacted".to_owned()),
|
||||
EventMsg::ContextCompacted(_) => {}
|
||||
EventMsg::CollabAgentSpawnBegin(_) => {}
|
||||
EventMsg::CollabAgentSpawnEnd(ev) => self.on_collab_event(collab::spawn_end(ev)),
|
||||
EventMsg::CollabAgentInteractionBegin(_) => {}
|
||||
@@ -3450,19 +3456,80 @@ impl ChatWidget {
|
||||
EventMsg::CollabCloseEnd(ev) => self.on_collab_event(collab::close_end(ev)),
|
||||
EventMsg::ThreadRolledBack(_) => {}
|
||||
EventMsg::RawResponseItem(_)
|
||||
| EventMsg::ItemStarted(_)
|
||||
| EventMsg::AgentMessageContentDelta(_)
|
||||
| EventMsg::ReasoningContentDelta(_)
|
||||
| EventMsg::ReasoningRawContentDelta(_)
|
||||
| EventMsg::DynamicToolCallRequest(_) => {}
|
||||
EventMsg::ItemCompleted(event) => {
|
||||
if let codex_protocol::items::TurnItem::Plan(plan_item) = event.item {
|
||||
self.on_plan_item_completed(plan_item.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_item_started(&mut self, event: ItemStartedEvent, from_replay: bool) {
|
||||
if from_replay {
|
||||
return;
|
||||
}
|
||||
match event.item {
|
||||
TurnItem::ContextCompaction(item) => self.on_context_compaction_started(item),
|
||||
TurnItem::UserMessage(_)
|
||||
| TurnItem::AgentMessage(_)
|
||||
| TurnItem::Reasoning(_)
|
||||
| TurnItem::Plan(_)
|
||||
| TurnItem::WebSearch(_) => {}
|
||||
};
|
||||
}
|
||||
|
||||
fn on_item_completed(&mut self, event: ItemCompletedEvent, from_replay: bool) {
|
||||
if from_replay {
|
||||
return;
|
||||
}
|
||||
match event.item {
|
||||
TurnItem::ContextCompaction(item) => self.on_context_compaction_completed(item),
|
||||
TurnItem::Plan(plan_item) => self.on_plan_item_completed(plan_item.text),
|
||||
TurnItem::UserMessage(_)
|
||||
| TurnItem::AgentMessage(_)
|
||||
| TurnItem::Reasoning(_)
|
||||
| TurnItem::WebSearch(_) => {}
|
||||
};
|
||||
}
|
||||
|
||||
fn on_context_compaction_started(&mut self, item: ContextCompactionItem) {
|
||||
self.flush_answer_stream_with_separator();
|
||||
if let Some(cell) = self.active_cell.as_mut().and_then(|cell| {
|
||||
cell.as_any_mut()
|
||||
.downcast_mut::<history_cell::ContextCompactionCell>()
|
||||
}) && cell.item_id() == item.id
|
||||
{
|
||||
self.bump_active_cell_revision();
|
||||
self.request_redraw();
|
||||
return;
|
||||
}
|
||||
|
||||
self.flush_active_cell();
|
||||
self.active_cell = Some(Box::new(history_cell::new_active_context_compaction(
|
||||
item.id,
|
||||
self.config.animations,
|
||||
)));
|
||||
self.bump_active_cell_revision();
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
fn on_context_compaction_completed(&mut self, item: ContextCompactionItem) {
|
||||
self.flush_answer_stream_with_separator();
|
||||
if let Some(cell) = self.active_cell.as_mut().and_then(|cell| {
|
||||
cell.as_any_mut()
|
||||
.downcast_mut::<history_cell::ContextCompactionCell>()
|
||||
}) && cell.item_id() == item.id
|
||||
{
|
||||
cell.complete();
|
||||
self.bump_active_cell_revision();
|
||||
self.flush_active_cell();
|
||||
self.request_redraw();
|
||||
return;
|
||||
}
|
||||
|
||||
self.add_to_history(history_cell::new_context_compaction_completed(item.id));
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
fn on_entered_review_mode(&mut self, review: ReviewRequest, from_replay: bool) {
|
||||
// Enter review mode and emit a concise banner
|
||||
if self.pre_review_token_info.is_none() {
|
||||
|
||||
@@ -30,6 +30,7 @@ use codex_core::protocol::AgentReasoningDeltaEvent;
|
||||
use codex_core::protocol::AgentReasoningEvent;
|
||||
use codex_core::protocol::ApplyPatchApprovalRequestEvent;
|
||||
use codex_core::protocol::BackgroundEventEvent;
|
||||
use codex_core::protocol::ContextCompactedEvent;
|
||||
use codex_core::protocol::CreditsSnapshot;
|
||||
use codex_core::protocol::Event;
|
||||
use codex_core::protocol::EventMsg;
|
||||
@@ -40,6 +41,8 @@ use codex_core::protocol::ExecCommandSource;
|
||||
use codex_core::protocol::ExecPolicyAmendment;
|
||||
use codex_core::protocol::ExitedReviewModeEvent;
|
||||
use codex_core::protocol::FileChange;
|
||||
use codex_core::protocol::ItemCompletedEvent;
|
||||
use codex_core::protocol::ItemStartedEvent;
|
||||
use codex_core::protocol::McpStartupCompleteEvent;
|
||||
use codex_core::protocol::McpStartupStatus;
|
||||
use codex_core::protocol::McpStartupUpdateEvent;
|
||||
@@ -68,6 +71,8 @@ use codex_protocol::config_types::CollaborationMode;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::Settings;
|
||||
use codex_protocol::items::ContextCompactionItem;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::ReasoningEffortPreset;
|
||||
use codex_protocol::parse_command::ParsedCommand;
|
||||
@@ -4580,6 +4585,58 @@ async fn warning_event_adds_warning_history_cell() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn compaction_items_drive_history_and_context_compacted_is_ignored() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
let thread_id = ThreadId::new();
|
||||
let compaction_item = ContextCompactionItem::new();
|
||||
let turn_id = "turn-1".to_string();
|
||||
|
||||
chat.handle_codex_event(Event {
|
||||
id: "item-start".into(),
|
||||
msg: EventMsg::ItemStarted(ItemStartedEvent {
|
||||
thread_id,
|
||||
turn_id: turn_id.clone(),
|
||||
item: TurnItem::ContextCompaction(compaction_item.clone()),
|
||||
}),
|
||||
});
|
||||
let started_cells = drain_insert_history(&mut rx);
|
||||
assert!(
|
||||
started_cells.is_empty(),
|
||||
"compaction start should keep an active cell instead of flushing history"
|
||||
);
|
||||
chat.handle_codex_event(Event {
|
||||
id: "context-compacted".into(),
|
||||
msg: EventMsg::ContextCompacted(ContextCompactedEvent {}),
|
||||
});
|
||||
let compacted_cells = drain_insert_history(&mut rx);
|
||||
assert!(
|
||||
compacted_cells.is_empty(),
|
||||
"ContextCompacted should not emit history cells"
|
||||
);
|
||||
chat.handle_codex_event(Event {
|
||||
id: "item-end".into(),
|
||||
msg: EventMsg::ItemCompleted(ItemCompletedEvent {
|
||||
thread_id,
|
||||
turn_id,
|
||||
item: TurnItem::ContextCompaction(compaction_item),
|
||||
}),
|
||||
});
|
||||
|
||||
let cells = drain_insert_history(&mut rx);
|
||||
assert_eq!(
|
||||
cells.len(),
|
||||
1,
|
||||
"expected a single finalized compaction cell"
|
||||
);
|
||||
|
||||
let completed = lines_to_single_string(&cells[0]);
|
||||
assert!(
|
||||
completed.contains("Context compacted."),
|
||||
"compaction completion message missing: {completed}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stream_recovery_restores_previous_status_header() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
|
||||
@@ -1465,6 +1465,76 @@ pub(crate) fn new_web_search_call(
|
||||
cell
|
||||
}
|
||||
|
||||
fn context_compaction_header(completed: bool) -> &'static str {
|
||||
if completed {
|
||||
"Context compacted."
|
||||
} else {
|
||||
"Compacting context"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ContextCompactionCell {
|
||||
item_id: String,
|
||||
start_time: Instant,
|
||||
completed: bool,
|
||||
animations_enabled: bool,
|
||||
}
|
||||
|
||||
impl ContextCompactionCell {
|
||||
pub(crate) fn new(item_id: String, animations_enabled: bool) -> Self {
|
||||
Self {
|
||||
item_id,
|
||||
start_time: Instant::now(),
|
||||
completed: false,
|
||||
animations_enabled,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn item_id(&self) -> &str {
|
||||
&self.item_id
|
||||
}
|
||||
|
||||
pub(crate) fn complete(&mut self) {
|
||||
self.completed = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl HistoryCell for ContextCompactionCell {
|
||||
fn display_lines(&self, width: u16) -> Vec<Line<'static>> {
|
||||
let bullet = if self.completed {
|
||||
Line::from(vec!["• ".dim()])
|
||||
} else {
|
||||
Line::from(vec![
|
||||
spinner(Some(self.start_time), self.animations_enabled),
|
||||
" ".into(),
|
||||
])
|
||||
};
|
||||
let header = context_compaction_header(self.completed);
|
||||
PrefixedWrappedHistoryCell::new(header.bold(), bullet, " ").display_lines(width)
|
||||
}
|
||||
|
||||
fn transcript_animation_tick(&self) -> Option<u64> {
|
||||
if !self.animations_enabled || self.completed {
|
||||
return None;
|
||||
}
|
||||
Some((self.start_time.elapsed().as_millis() / 50) as u64)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_active_context_compaction(
|
||||
item_id: String,
|
||||
animations_enabled: bool,
|
||||
) -> ContextCompactionCell {
|
||||
ContextCompactionCell::new(item_id, animations_enabled)
|
||||
}
|
||||
|
||||
pub(crate) fn new_context_compaction_completed(item_id: String) -> ContextCompactionCell {
|
||||
let mut cell = ContextCompactionCell::new(item_id, false);
|
||||
cell.complete();
|
||||
cell
|
||||
}
|
||||
|
||||
/// Returns an additional history cell if an MCP tool result includes a decodable image.
|
||||
///
|
||||
/// This intentionally returns at most one cell: the first image in `CallToolResult.content` that
|
||||
@@ -2352,6 +2422,22 @@ mod tests {
|
||||
insta::assert_snapshot!(rendered);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_compaction_active_snapshot() {
|
||||
let cell = new_active_context_compaction("compaction-1".to_string(), false);
|
||||
let rendered = render_lines(&cell.display_lines(64)).join("\n");
|
||||
|
||||
insta::assert_snapshot!(rendered);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_compaction_completed_snapshot() {
|
||||
let cell = new_context_compaction_completed("compaction-1".to_string());
|
||||
let rendered = render_lines(&cell.display_lines(64)).join("\n");
|
||||
|
||||
insta::assert_snapshot!(rendered);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn active_mcp_tool_call_snapshot() {
|
||||
let invocation = McpInvocation {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tui/src/history_cell.rs
|
||||
expression: rendered
|
||||
---
|
||||
• Compacting context
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tui/src/history_cell.rs
|
||||
expression: rendered
|
||||
---
|
||||
• Context compacted.
|
||||
Reference in New Issue
Block a user