mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
Changed codex resume --last to honor the current cwd (#9245)
This PR changes `codex resume --last` to work consistently with `codex resume`. Namely, it filters based on the cwd when selecting the last session. It also supports the `--all` modifier as an override. This addresses #8700
This commit is contained in:
@@ -28,6 +28,7 @@ use super::policy::is_persisted_response_item;
|
||||
use crate::config::Config;
|
||||
use crate::default_client::originator;
|
||||
use crate::git_info::collect_git_info;
|
||||
use crate::path_utils;
|
||||
use codex_protocol::protocol::InitialHistory;
|
||||
use codex_protocol::protocol::ResumedHistory;
|
||||
use codex_protocol::protocol::RolloutItem;
|
||||
@@ -113,6 +114,35 @@ impl RolloutRecorder {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Find the newest recorded thread path, optionally filtering to a matching cwd.
|
||||
pub async fn find_latest_thread_path(
|
||||
codex_home: &Path,
|
||||
allowed_sources: &[SessionSource],
|
||||
model_providers: Option<&[String]>,
|
||||
default_provider: &str,
|
||||
filter_cwd: Option<&Path>,
|
||||
) -> std::io::Result<Option<PathBuf>> {
|
||||
let mut cursor: Option<Cursor> = None;
|
||||
loop {
|
||||
let page = Self::list_threads(
|
||||
codex_home,
|
||||
25,
|
||||
cursor.as_ref(),
|
||||
allowed_sources,
|
||||
model_providers,
|
||||
default_provider,
|
||||
)
|
||||
.await?;
|
||||
if let Some(path) = select_resume_path(&page, filter_cwd) {
|
||||
return Ok(Some(path));
|
||||
}
|
||||
cursor = page.next_cursor;
|
||||
if cursor.is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to create a new [`RolloutRecorder`]. If the sessions directory
|
||||
/// cannot be created or the rollout file cannot be opened we return the
|
||||
/// error so the caller can decide whether to disable persistence.
|
||||
@@ -431,3 +461,36 @@ impl JsonlWriter {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn select_resume_path(page: &ThreadsPage, filter_cwd: Option<&Path>) -> Option<PathBuf> {
|
||||
match filter_cwd {
|
||||
Some(cwd) => page.items.iter().find_map(|item| {
|
||||
if session_cwd_matches(&item.head, cwd) {
|
||||
Some(item.path.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
None => page.items.first().map(|item| item.path.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn session_cwd_matches(head: &[serde_json::Value], cwd: &Path) -> bool {
|
||||
let Some(session_cwd) = extract_session_cwd(head) else {
|
||||
return false;
|
||||
};
|
||||
if let (Ok(ca), Ok(cb)) = (
|
||||
path_utils::normalize_for_path_comparison(&session_cwd),
|
||||
path_utils::normalize_for_path_comparison(cwd),
|
||||
) {
|
||||
return ca == cb;
|
||||
}
|
||||
session_cwd == cwd
|
||||
}
|
||||
|
||||
fn extract_session_cwd(head: &[serde_json::Value]) -> Option<PathBuf> {
|
||||
head.iter().find_map(|value| {
|
||||
let meta_line = serde_json::from_value::<SessionMetaLine>(value.clone()).ok()?;
|
||||
Some(meta_line.meta.cwd)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -522,22 +522,22 @@ async fn run_ratatui_app(
|
||||
}
|
||||
} else if cli.resume_last {
|
||||
let provider_filter = vec![config.model_provider_id.clone()];
|
||||
match RolloutRecorder::list_threads(
|
||||
let filter_cwd = if cli.resume_show_all {
|
||||
None
|
||||
} else {
|
||||
Some(config.cwd.as_path())
|
||||
};
|
||||
match RolloutRecorder::find_latest_thread_path(
|
||||
&config.codex_home,
|
||||
1,
|
||||
None,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
Some(provider_filter.as_slice()),
|
||||
&config.model_provider_id,
|
||||
filter_cwd,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(page) => page
|
||||
.items
|
||||
.first()
|
||||
.map(|it| resume_picker::SessionSelection::Resume(it.path.clone()))
|
||||
.unwrap_or(resume_picker::SessionSelection::StartFresh),
|
||||
Err(_) => resume_picker::SessionSelection::StartFresh,
|
||||
Ok(Some(path)) => resume_picker::SessionSelection::Resume(path),
|
||||
_ => resume_picker::SessionSelection::StartFresh,
|
||||
}
|
||||
} else if cli.resume_picker {
|
||||
match resume_picker::run_resume_picker(
|
||||
|
||||
@@ -545,22 +545,22 @@ async fn run_ratatui_app(
|
||||
}
|
||||
} else if cli.resume_last {
|
||||
let provider_filter = vec![config.model_provider_id.clone()];
|
||||
match RolloutRecorder::list_threads(
|
||||
let filter_cwd = if cli.resume_show_all {
|
||||
None
|
||||
} else {
|
||||
Some(config.cwd.as_path())
|
||||
};
|
||||
match RolloutRecorder::find_latest_thread_path(
|
||||
&config.codex_home,
|
||||
1,
|
||||
None,
|
||||
INTERACTIVE_SESSION_SOURCES,
|
||||
Some(provider_filter.as_slice()),
|
||||
&config.model_provider_id,
|
||||
filter_cwd,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(page) => page
|
||||
.items
|
||||
.first()
|
||||
.map(|it| resume_picker::SessionSelection::Resume(it.path.clone()))
|
||||
.unwrap_or(resume_picker::SessionSelection::StartFresh),
|
||||
Err(_) => resume_picker::SessionSelection::StartFresh,
|
||||
Ok(Some(path)) => resume_picker::SessionSelection::Resume(path),
|
||||
_ => resume_picker::SessionSelection::StartFresh,
|
||||
}
|
||||
} else if cli.resume_picker {
|
||||
match resume_picker::run_resume_picker(
|
||||
|
||||
Reference in New Issue
Block a user