diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 169f2c213f..81d9881026 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -2523,7 +2523,9 @@ impl App { AppEvent::OpenResumePicker => { match crate::resume_picker::run_resume_picker(tui, &self.config, false).await? { SessionSelection::Resume(target_session) => { - self.chat_widget.handle_resume_selection(target_session); + self.chat_widget.handle_serialized_slash_command( + ChatWidget::resume_selection_draft(&target_session), + ); self.refresh_status_line(); } SessionSelection::Exit diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 62bb7ec40a..c0b19a2c21 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -268,7 +268,6 @@ use crate::render::renderable::FlexRenderable; use crate::render::renderable::Renderable; use crate::render::renderable::RenderableExt; use crate::render::renderable::RenderableItem; -use crate::resume_picker::SessionTarget; use crate::slash_command::SlashCommand; use crate::status::RateLimitSnapshotDisplay; use crate::status_indicator_widget::STATUS_DETAILS_DEFAULT_MAX_LINES; @@ -736,11 +735,6 @@ pub(crate) struct ChatWidget { // drafts and are replayed through the slash-command evaluator instead of being submitted // directly as user turns. queued_user_messages: VecDeque, - // Exact rollout targets picked from the resume picker for queued `/resume ` drafts. - // - // The serialized draft stays human-readable, but replay consumes these targets in queue order - // so the selected rollout path is preserved instead of being re-resolved by thread id later. - queued_resume_targets: VecDeque, // Steers already submitted to core but not yet committed into history. // // The bottom pane shows these above queued drafts until core records the @@ -899,7 +893,6 @@ pub(crate) struct ThreadInputState { composer: Option, pending_steers: VecDeque, queued_user_messages: VecDeque, - queued_resume_targets: VecDeque, current_collaboration_mode: CollaborationMode, active_collaboration_mask: Option, agent_turn_running: bool, @@ -2275,7 +2268,6 @@ impl ChatWidget { .map(|steer| steer.user_message) .collect(); to_merge.extend(self.queued_user_messages.drain(..)); - self.queued_resume_targets.clear(); if has_existing_message { to_merge.push(existing_message); } @@ -2318,7 +2310,6 @@ impl ChatWidget { .map(|pending| pending.user_message.clone()) .collect(), queued_user_messages: self.queued_user_messages.clone(), - queued_resume_targets: self.queued_resume_targets.clone(), current_collaboration_mode: self.current_collaboration_mode.clone(), active_collaboration_mask: self.active_collaboration_mask.clone(), agent_turn_running: self.agent_turn_running, @@ -2361,7 +2352,6 @@ impl ChatWidget { self.queued_user_messages = input_state.pending_steers; self.queued_user_messages .extend(input_state.queued_user_messages); - self.queued_resume_targets = input_state.queued_resume_targets; } else { self.agent_turn_running = false; self.pending_steers.clear(); @@ -2374,7 +2364,6 @@ impl ChatWidget { ); self.bottom_pane.set_composer_pending_pastes(Vec::new()); self.queued_user_messages.clear(); - self.queued_resume_targets.clear(); } self.turn_sleep_inhibitor .set_turn_running(self.agent_turn_running); @@ -3656,7 +3645,6 @@ impl ChatWidget { thread_name: None, forked_from: None, queued_user_messages: VecDeque::new(), - queued_resume_targets: VecDeque::new(), pending_steers: VecDeque::new(), submit_pending_steers_after_interrupt: false, queued_message_edit_binding, @@ -3848,7 +3836,6 @@ impl ChatWidget { plan_delta_buffer: String::new(), plan_item_active: false, queued_user_messages: VecDeque::new(), - queued_resume_targets: VecDeque::new(), pending_steers: VecDeque::new(), submit_pending_steers_after_interrupt: false, queued_message_edit_binding, @@ -4024,7 +4011,6 @@ impl ChatWidget { thread_name: None, forked_from: None, queued_user_messages: VecDeque::new(), - queued_resume_targets: VecDeque::new(), pending_steers: VecDeque::new(), submit_pending_steers_after_interrupt: false, queued_message_edit_binding, @@ -4159,12 +4145,6 @@ impl ChatWidget { && !self.queued_user_messages.is_empty() { if let Some(queued_message) = self.queued_user_messages.pop_back() { - if self - .parse_builtin_slash_command(&queued_message.text) - .is_some_and(|(cmd, _, _)| cmd == SlashCommand::Resume) - { - self.queued_resume_targets.pop_back(); - } self.restore_user_message_to_composer(queued_message); self.refresh_pending_input_preview(); self.request_redraw(); @@ -5324,7 +5304,7 @@ impl ChatWidget { SlashCommand::Resume => { let args = match SlashCommandInvocation::parse_args( &args_message.text, - "Usage: /resume ", + "Usage: /resume [--path ]", ) { Ok(args) => args, Err(message) => { @@ -5335,26 +5315,22 @@ impl ChatWidget { ); } }; - if args.len() != 1 { + if !(args.len() == 1 || (args.len() == 3 && args[1] == "--path")) { return self.restore_invalid_inline_slash_command( cmd, args_message, - "Usage: /resume ".to_string(), + "Usage: /resume [--path ]".to_string(), ); } match ThreadId::from_string(&args[0]) { Ok(thread_id) => { - if self - .queued_resume_targets - .front() - .is_some_and(|target| target.thread_id == thread_id) - { - if let Some(target_session) = self.queued_resume_targets.pop_front() { - self.app_event_tx - .send(AppEvent::ResumeSessionTarget(target_session)); - } else { - self.app_event_tx.send(AppEvent::ResumeSession(thread_id)); - } + if let Some(path) = args.get(2) { + self.app_event_tx.send(AppEvent::ResumeSessionTarget( + crate::resume_picker::SessionTarget { + path: PathBuf::from(path), + thread_id, + }, + )); } else { self.app_event_tx.send(AppEvent::ResumeSession(thread_id)); } @@ -5363,7 +5339,7 @@ impl ChatWidget { Err(_) => self.restore_invalid_inline_slash_command( cmd, args_message, - "Usage: /resume ".to_string(), + "Usage: /resume [--path ]".to_string(), ), } } @@ -5729,6 +5705,20 @@ impl ChatWidget { } } + pub(crate) fn resume_selection_draft( + target_session: &crate::resume_picker::SessionTarget, + ) -> UserMessage { + SlashCommandInvocation::with_args( + SlashCommand::Resume, + [ + target_session.thread_id.to_string(), + "--path".to_string(), + target_session.path.display().to_string(), + ], + ) + .into_user_message() + } + pub(crate) fn handle_serialized_slash_command(&mut self, draft: UserMessage) { let Some((cmd, _, _)) = self.parse_builtin_slash_command(&draft.text) else { if self.reject_unavailable_builtin_slash_command(&draft) { @@ -5745,17 +5735,6 @@ impl ChatWidget { let _ = self.execute_serialized_slash_command(draft); } - pub(crate) fn handle_resume_selection(&mut self, target_session: SessionTarget) { - self.queued_resume_targets.push_back(target_session.clone()); - self.handle_serialized_slash_command( - SlashCommandInvocation::with_args( - SlashCommand::Resume, - [target_session.thread_id.to_string()], - ) - .into_user_message(), - ); - } - fn submit_plan_user_message(&mut self, user_message: UserMessage) { if self.is_session_configured() { self.reasoning_buffer.clear(); diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_output.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_output.snap index ca20b0e2df..84018cf3ea 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_output.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_output.snap @@ -83,6 +83,7 @@ expression: popup Usage: /resume /resume + /resume --path /fork fork the current chat @@ -240,4 +241,4 @@ expression: popup /debug-m-drop - ↑/↓ scroll | [ctrl + p / ctrl + n] page | / search | esc close 1-232/239 + ↑/↓ scroll | [ctrl + p / ctrl + n] page | / search | esc close 1-233/240 diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_output@windows.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_output@windows.snap index abc24b16f5..760b42e823 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_output@windows.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_output@windows.snap @@ -88,6 +88,7 @@ expression: popup Usage: /resume /resume + /resume --path /fork fork the current chat diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_search_output.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_search_output.snap index 9752769d79..b11a4f7f04 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_search_output.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_search_output.snap @@ -83,6 +83,7 @@ expression: searching Usage: /resume /resume + /resume --path /fork fork the current chat @@ -240,4 +241,4 @@ expression: searching /debug-m-drop - Search: /maintainers | enter apply | esc cancel 1 match | 1-232/239 + Search: /maintainers | enter apply | esc cancel 1 match | 1-233/240 diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_search_output@windows.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_search_output@windows.snap index 0e0fa91b7c..90da87604e 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_search_output@windows.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_help_search_output@windows.snap @@ -88,6 +88,7 @@ expression: searching Usage: /resume /resume + /resume --path /fork fork the current chat diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 009ff730d4..b5f1c54bd9 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -1902,7 +1902,6 @@ async fn make_chatwidget_manual( show_welcome_banner: true, startup_tooltip_override: None, queued_user_messages: VecDeque::new(), - queued_resume_targets: VecDeque::new(), pending_steers: VecDeque::new(), submit_pending_steers_after_interrupt: false, queued_message_edit_binding: crate::key_hint::alt(KeyCode::Up), @@ -3682,7 +3681,6 @@ async fn restore_thread_input_state_syncs_sleep_inhibitor_state() { composer: None, pending_steers: VecDeque::new(), queued_user_messages: VecDeque::new(), - queued_resume_targets: VecDeque::new(), current_collaboration_mode: chat.current_collaboration_mode.clone(), active_collaboration_mask: chat.active_collaboration_mask.clone(), agent_turn_running: true, @@ -5727,8 +5725,8 @@ async fn queued_resume_picker_selection_replays_exact_targets_in_order() { thread_id, }; - chat.handle_resume_selection(first.clone()); - chat.handle_resume_selection(second.clone()); + chat.handle_serialized_slash_command(ChatWidget::resume_selection_draft(&first)); + chat.handle_serialized_slash_command(ChatWidget::resume_selection_draft(&second)); chat.bottom_pane.set_task_running(false); chat.drain_queued_inputs_until_blocked(); diff --git a/codex-rs/tui/src/slash_command.rs b/codex-rs/tui/src/slash_command.rs index c6092be030..30ba911770 100644 --- a/codex-rs/tui/src/slash_command.rs +++ b/codex-rs/tui/src/slash_command.rs @@ -150,7 +150,7 @@ impl SlashCommand { ], SlashCommand::Rename => &["", ""], SlashCommand::New => &[""], - SlashCommand::Resume => &["", ""], + SlashCommand::Resume => &["", "", " --path "], SlashCommand::Fork => &[""], SlashCommand::Init => &[""], SlashCommand::Compact => &[""],