diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 98d13b4cd6..414984dd81 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -760,6 +760,10 @@ async fn submission_loop( } }; + // Determine rollout path if available before moving the recorder. + let rollout_path: Option = + rollout_recorder.as_ref().map(|r| r.path().to_path_buf()); + let client = ModelClient::new( config.clone(), auth.clone(), @@ -859,6 +863,7 @@ async fn submission_loop( model, history_log_id, history_entry_count, + rollout_path, }), }) .chain(mcp_connection_errors.into_iter()); diff --git a/codex-rs/core/src/protocol.rs b/codex-rs/core/src/protocol.rs index 55000fb6d7..197f04a6f5 100644 --- a/codex-rs/core/src/protocol.rs +++ b/codex-rs/core/src/protocol.rs @@ -647,6 +647,10 @@ pub struct SessionConfiguredEvent { /// Current number of entries in the history log. pub history_entry_count: usize, + + /// Absolute path to the rollout file for this session, if recording is enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub rollout_path: Option, } /// User's decision in response to an ExecApprovalRequest. @@ -709,6 +713,7 @@ mod tests { model: "codex-mini-latest".to_string(), history_log_id: 0, history_entry_count: 0, + rollout_path: None, }), }; let serialized = serde_json::to_string(&event).unwrap(); diff --git a/codex-rs/core/src/rollout.rs b/codex-rs/core/src/rollout.rs index 0ccd8e891b..af01a6c53d 100644 --- a/codex-rs/core/src/rollout.rs +++ b/codex-rs/core/src/rollout.rs @@ -66,6 +66,8 @@ pub struct SavedSession { #[derive(Clone)] pub(crate) struct RolloutRecorder { tx: Sender, + /// Absolute path to the rollout file for this session. + path: std::path::PathBuf, } enum RolloutCmd { @@ -87,6 +89,7 @@ impl RolloutRecorder { file, session_id, timestamp, + path, } = create_log_file(config, uuid)?; let timestamp_format: &[FormatItem] = format_description!( @@ -118,7 +121,7 @@ impl RolloutRecorder { cwd, )); - Ok(Self { tx }) + Ok(Self { tx, path }) } pub(crate) async fn record_items(&self, items: &[ResponseItem]) -> std::io::Result<()> { @@ -223,7 +226,13 @@ impl RolloutRecorder { cwd, )); info!("Resumed rollout successfully from {path:?}"); - Ok((Self { tx }, saved)) + Ok(( + Self { + tx, + path: path.to_path_buf(), + }, + saved, + )) } pub async fn shutdown(&self) -> std::io::Result<()> { @@ -240,6 +249,11 @@ impl RolloutRecorder { } } } + + /// Return the absolute path to the rollout file recorded by this session. + pub fn path(&self) -> &std::path::Path { + &self.path + } } struct LogFileInfo { @@ -251,6 +265,9 @@ struct LogFileInfo { /// Timestamp for the start of the session. timestamp: OffsetDateTime, + + /// Absolute path to the rollout file on disk. + path: std::path::PathBuf, } fn create_log_file(config: &Config, session_id: Uuid) -> std::io::Result { @@ -284,6 +301,7 @@ fn create_log_file(config: &Config, session_id: Uuid) -> std::io::Result { let SessionConfiguredEvent { - session_id, - model, - history_log_id: _, - history_entry_count: _, + session_id, model, .. } = session_configured_event; ts_println!( diff --git a/codex-rs/mcp-server/src/mcp_protocol.rs b/codex-rs/mcp-server/src/mcp_protocol.rs index 2f8858a37b..aaa1ac0498 100644 --- a/codex-rs/mcp-server/src/mcp_protocol.rs +++ b/codex-rs/mcp-server/src/mcp_protocol.rs @@ -908,6 +908,7 @@ mod tests { model: "codex-mini-latest".into(), history_log_id: 42, history_entry_count: 3, + rollout_path: None, }), }; diff --git a/codex-rs/mcp-server/src/outgoing_message.rs b/codex-rs/mcp-server/src/outgoing_message.rs index e7b0b9b63c..0cd57e6653 100644 --- a/codex-rs/mcp-server/src/outgoing_message.rs +++ b/codex-rs/mcp-server/src/outgoing_message.rs @@ -244,6 +244,7 @@ mod tests { model: "gpt-4o".to_string(), history_log_id: 1, history_entry_count: 1000, + rollout_path: None, }), }; @@ -284,6 +285,7 @@ mod tests { model: "gpt-4o".to_string(), history_log_id: 1, history_entry_count: 1000, + rollout_path: None, }; let event = Event { id: "1".to_string(), diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 69f1600cd2..5f5cd033c2 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -79,6 +79,8 @@ pub(crate) struct ChatWidget<'a> { current_stream: Option, stream_header_emitted: bool, live_max_rows: u16, + /// Absolute path to the rollout file for the active session (if available). + rollout_path: Option, } struct UserMessage { @@ -207,6 +209,7 @@ impl ChatWidget<'_> { current_stream: None, stream_header_emitted: false, live_max_rows: 3, + rollout_path: None, } } @@ -283,6 +286,8 @@ impl ChatWidget<'_> { EventMsg::SessionConfigured(event) => { self.bottom_pane .set_history_metadata(event.history_log_id, event.history_entry_count); + // Record rollout path for status reporting. + self.rollout_path = event.rollout_path.clone(); // Record session information at the top of the conversation. self.add_to_history(HistoryCell::new_session_info(&self.config, event, true)); @@ -552,6 +557,7 @@ impl ChatWidget<'_> { self.add_to_history(HistoryCell::new_status_output( &self.config, &self.token_usage, + self.rollout_path.as_ref(), )); } diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index c577ce17a0..df27b9971b 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -166,12 +166,7 @@ impl HistoryCell { event: SessionConfiguredEvent, is_first_event: bool, ) -> Self { - let SessionConfiguredEvent { - model, - session_id: _, - history_log_id: _, - history_entry_count: _, - } = event; + let SessionConfiguredEvent { model, .. } = event; if is_first_event { let cwd_str = match relativize_to_home(&config.cwd) { Some(rel) if !rel.as_os_str().is_empty() => format!("~/{}", rel.display()), @@ -450,7 +445,11 @@ impl HistoryCell { } } - pub(crate) fn new_status_output(config: &Config, usage: &TokenUsage) -> Self { + pub(crate) fn new_status_output( + config: &Config, + usage: &TokenUsage, + rollout_path: Option<&std::path::PathBuf>, + ) -> Self { let mut lines: Vec> = Vec::new(); lines.push(Line::from("/status".magenta())); @@ -459,6 +458,14 @@ impl HistoryCell { lines.push(Line::from(vec![format!("{key}: ").bold(), value.into()])); } + // Rollout file path (if available) + if let Some(p) = rollout_path { + lines.push(Line::from(vec![ + "rollout: ".bold(), + p.display().to_string().into(), + ])); + } + // Token usage lines.push(Line::from("")); lines.push(Line::from("token usage".bold()));