mirror of
https://github.com/openai/codex.git
synced 2026-02-01 22:47:52 +00:00
tui: add branch to 'codex resume', filter by cwd (#6232)
By default, show only sessions that shared a cwd with the current cwd. `--all` shows all sessions in all cwds. Also, show the branch name from the rollout metadata. <img width="1091" height="638" alt="Screenshot 2025-11-04 at 3 30 47 PM" src="https://github.com/user-attachments/assets/aae90308-6115-455f-aff7-22da5f1d9681" />
This commit is contained in:
@@ -134,6 +134,10 @@ struct ResumeCommand {
|
||||
#[arg(long = "last", default_value_t = false, conflicts_with = "session_id")]
|
||||
last: bool,
|
||||
|
||||
/// Show all sessions (disables cwd filtering and shows CWD column).
|
||||
#[arg(long = "all", default_value_t = false)]
|
||||
all: bool,
|
||||
|
||||
#[clap(flatten)]
|
||||
config_overrides: TuiCli,
|
||||
}
|
||||
@@ -448,6 +452,7 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
|
||||
Some(Subcommand::Resume(ResumeCommand {
|
||||
session_id,
|
||||
last,
|
||||
all,
|
||||
config_overrides,
|
||||
})) => {
|
||||
interactive = finalize_resume_interactive(
|
||||
@@ -455,6 +460,7 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
|
||||
root_config_overrides.clone(),
|
||||
session_id,
|
||||
last,
|
||||
all,
|
||||
config_overrides,
|
||||
);
|
||||
let exit_info = codex_tui::run_main(interactive, codex_linux_sandbox_exe).await?;
|
||||
@@ -611,6 +617,7 @@ fn finalize_resume_interactive(
|
||||
root_config_overrides: CliConfigOverrides,
|
||||
session_id: Option<String>,
|
||||
last: bool,
|
||||
show_all: bool,
|
||||
resume_cli: TuiCli,
|
||||
) -> TuiCli {
|
||||
// Start with the parsed interactive CLI so resume shares the same
|
||||
@@ -619,6 +626,7 @@ fn finalize_resume_interactive(
|
||||
interactive.resume_picker = resume_session_id.is_none() && !last;
|
||||
interactive.resume_last = last;
|
||||
interactive.resume_session_id = resume_session_id;
|
||||
interactive.resume_show_all = show_all;
|
||||
|
||||
// Merge resume-scoped flags and overrides with highest precedence.
|
||||
merge_resume_cli_flags(&mut interactive, resume_cli);
|
||||
@@ -702,13 +710,21 @@ mod tests {
|
||||
let Subcommand::Resume(ResumeCommand {
|
||||
session_id,
|
||||
last,
|
||||
all,
|
||||
config_overrides: resume_cli,
|
||||
}) = subcommand.expect("resume present")
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
finalize_resume_interactive(interactive, root_overrides, session_id, last, resume_cli)
|
||||
finalize_resume_interactive(
|
||||
interactive,
|
||||
root_overrides,
|
||||
session_id,
|
||||
last,
|
||||
all,
|
||||
resume_cli,
|
||||
)
|
||||
}
|
||||
|
||||
fn sample_exit_info(conversation: Option<&str>) -> AppExitInfo {
|
||||
@@ -775,6 +791,7 @@ mod tests {
|
||||
assert!(interactive.resume_picker);
|
||||
assert!(!interactive.resume_last);
|
||||
assert_eq!(interactive.resume_session_id, None);
|
||||
assert!(!interactive.resume_show_all);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -783,6 +800,7 @@ mod tests {
|
||||
assert!(!interactive.resume_picker);
|
||||
assert!(interactive.resume_last);
|
||||
assert_eq!(interactive.resume_session_id, None);
|
||||
assert!(!interactive.resume_show_all);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -791,6 +809,14 @@ mod tests {
|
||||
assert!(!interactive.resume_picker);
|
||||
assert!(!interactive.resume_last);
|
||||
assert_eq!(interactive.resume_session_id.as_deref(), Some("1234"));
|
||||
assert!(!interactive.resume_show_all);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resume_all_flag_sets_show_all() {
|
||||
let interactive = finalize_from_args(["codex", "resume", "--all"].as_ref());
|
||||
assert!(interactive.resume_picker);
|
||||
assert!(interactive.resume_show_all);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -28,6 +28,10 @@ pub struct Cli {
|
||||
#[clap(skip)]
|
||||
pub resume_session_id: Option<String>,
|
||||
|
||||
/// Internal: show all sessions (disables cwd filtering and shows CWD column).
|
||||
#[clap(skip)]
|
||||
pub resume_show_all: bool,
|
||||
|
||||
/// Model the agent should use.
|
||||
#[arg(long, short = 'm')]
|
||||
pub model: Option<String>,
|
||||
|
||||
@@ -474,6 +474,7 @@ async fn run_ratatui_app(
|
||||
&mut tui,
|
||||
&config.codex_home,
|
||||
&config.model_provider_id,
|
||||
cli.resume_show_all,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
|
||||
@@ -26,12 +26,14 @@ use tokio_stream::StreamExt;
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::diff_render::display_path_for;
|
||||
use crate::key_hint;
|
||||
use crate::text_formatting::truncate_text;
|
||||
use crate::tui::FrameRequester;
|
||||
use crate::tui::Tui;
|
||||
use crate::tui::TuiEvent;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::SessionMetaLine;
|
||||
|
||||
const PAGE_SIZE: usize = 25;
|
||||
const LOAD_NEAR_THRESHOLD: usize = 5;
|
||||
@@ -69,11 +71,17 @@ pub async fn run_resume_picker(
|
||||
tui: &mut Tui,
|
||||
codex_home: &Path,
|
||||
default_provider: &str,
|
||||
show_all: bool,
|
||||
) -> Result<ResumeSelection> {
|
||||
let alt = AltScreenGuard::enter(tui);
|
||||
let (bg_tx, bg_rx) = mpsc::unbounded_channel();
|
||||
|
||||
let default_provider = default_provider.to_string();
|
||||
let filter_cwd = if show_all {
|
||||
None
|
||||
} else {
|
||||
std::env::current_dir().ok()
|
||||
};
|
||||
|
||||
let loader_tx = bg_tx.clone();
|
||||
let page_loader: PageLoader = Arc::new(move |request: PageLoadRequest| {
|
||||
@@ -102,6 +110,8 @@ pub async fn run_resume_picker(
|
||||
alt.tui.frame_requester(),
|
||||
page_loader,
|
||||
default_provider.clone(),
|
||||
show_all,
|
||||
filter_cwd,
|
||||
);
|
||||
state.load_initial_page().await?;
|
||||
state.request_frame();
|
||||
@@ -177,6 +187,8 @@ struct PickerState {
|
||||
page_loader: PageLoader,
|
||||
view_rows: Option<usize>,
|
||||
default_provider: String,
|
||||
show_all: bool,
|
||||
filter_cwd: Option<PathBuf>,
|
||||
}
|
||||
|
||||
struct PaginationState {
|
||||
@@ -234,6 +246,8 @@ struct Row {
|
||||
preview: String,
|
||||
created_at: Option<DateTime<Utc>>,
|
||||
updated_at: Option<DateTime<Utc>>,
|
||||
cwd: Option<PathBuf>,
|
||||
git_branch: Option<String>,
|
||||
}
|
||||
|
||||
impl PickerState {
|
||||
@@ -242,6 +256,8 @@ impl PickerState {
|
||||
requester: FrameRequester,
|
||||
page_loader: PageLoader,
|
||||
default_provider: String,
|
||||
show_all: bool,
|
||||
filter_cwd: Option<PathBuf>,
|
||||
) -> Self {
|
||||
Self {
|
||||
codex_home,
|
||||
@@ -264,6 +280,8 @@ impl PickerState {
|
||||
page_loader,
|
||||
view_rows: None,
|
||||
default_provider,
|
||||
show_all,
|
||||
filter_cwd,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,13 +436,15 @@ impl PickerState {
|
||||
}
|
||||
|
||||
fn apply_filter(&mut self) {
|
||||
let base_iter = self
|
||||
.all_rows
|
||||
.iter()
|
||||
.filter(|row| self.row_matches_filter(row));
|
||||
if self.query.is_empty() {
|
||||
self.filtered_rows = self.all_rows.clone();
|
||||
self.filtered_rows = base_iter.cloned().collect();
|
||||
} else {
|
||||
let q = self.query.to_lowercase();
|
||||
self.filtered_rows = self
|
||||
.all_rows
|
||||
.iter()
|
||||
self.filtered_rows = base_iter
|
||||
.filter(|r| r.preview.to_lowercase().contains(&q))
|
||||
.cloned()
|
||||
.collect();
|
||||
@@ -439,6 +459,19 @@ impl PickerState {
|
||||
self.request_frame();
|
||||
}
|
||||
|
||||
fn row_matches_filter(&self, row: &Row) -> bool {
|
||||
if self.show_all {
|
||||
return true;
|
||||
}
|
||||
let Some(filter_cwd) = self.filter_cwd.as_ref() else {
|
||||
return true;
|
||||
};
|
||||
let Some(row_cwd) = row.cwd.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
paths_match(row_cwd, filter_cwd)
|
||||
}
|
||||
|
||||
fn set_query(&mut self, new_query: String) {
|
||||
if self.query == new_query {
|
||||
return;
|
||||
@@ -606,6 +639,7 @@ fn head_to_row(item: &ConversationItem) -> Row {
|
||||
.and_then(parse_timestamp_str)
|
||||
.or(created_at);
|
||||
|
||||
let (cwd, git_branch) = extract_session_meta_from_head(&item.head);
|
||||
let preview = preview_from_head(&item.head)
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
@@ -616,9 +650,29 @@ fn head_to_row(item: &ConversationItem) -> Row {
|
||||
preview,
|
||||
created_at,
|
||||
updated_at,
|
||||
cwd,
|
||||
git_branch,
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_session_meta_from_head(head: &[serde_json::Value]) -> (Option<PathBuf>, Option<String>) {
|
||||
for value in head {
|
||||
if let Ok(meta_line) = serde_json::from_value::<SessionMetaLine>(value.clone()) {
|
||||
let cwd = Some(meta_line.meta.cwd);
|
||||
let git_branch = meta_line.git.and_then(|git| git.branch);
|
||||
return (cwd, git_branch);
|
||||
}
|
||||
}
|
||||
(None, None)
|
||||
}
|
||||
|
||||
fn paths_match(a: &Path, b: &Path) -> bool {
|
||||
if let (Ok(ca), Ok(cb)) = (a.canonicalize(), b.canonicalize()) {
|
||||
return ca == cb;
|
||||
}
|
||||
a == b
|
||||
}
|
||||
|
||||
fn parse_timestamp_str(ts: &str) -> Option<DateTime<Utc>> {
|
||||
chrono::DateTime::parse_from_rfc3339(ts)
|
||||
.map(|dt| dt.with_timezone(&Utc))
|
||||
@@ -670,7 +724,7 @@ fn draw_picker(tui: &mut Tui, state: &PickerState) -> std::io::Result<()> {
|
||||
};
|
||||
frame.render_widget_ref(Line::from(q), search);
|
||||
|
||||
let metrics = calculate_column_metrics(&state.filtered_rows);
|
||||
let metrics = calculate_column_metrics(&state.filtered_rows, state.show_all);
|
||||
|
||||
// Column headers and list
|
||||
render_column_headers(frame, columns, &metrics);
|
||||
@@ -720,10 +774,11 @@ fn render_list(
|
||||
let labels = &metrics.labels;
|
||||
let mut y = area.y;
|
||||
|
||||
let max_created_width = metrics.max_created_width;
|
||||
let max_updated_width = metrics.max_updated_width;
|
||||
let max_branch_width = metrics.max_branch_width;
|
||||
let max_cwd_width = metrics.max_cwd_width;
|
||||
|
||||
for (idx, (row, (created_label, updated_label))) in rows[start..end]
|
||||
for (idx, (row, (updated_label, branch_label, cwd_label))) in rows[start..end]
|
||||
.iter()
|
||||
.zip(labels[start..end].iter())
|
||||
.enumerate()
|
||||
@@ -731,38 +786,69 @@ fn render_list(
|
||||
let is_sel = start + idx == state.selected;
|
||||
let marker = if is_sel { "> ".bold() } else { " ".into() };
|
||||
let marker_width = 2usize;
|
||||
let created_span = if max_created_width == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Span::from(format!("{created_label:<max_created_width$}")).dim())
|
||||
};
|
||||
let updated_span = if max_updated_width == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Span::from(format!("{updated_label:<max_updated_width$}")).dim())
|
||||
};
|
||||
let branch_span = if max_branch_width == 0 {
|
||||
None
|
||||
} else if branch_label.is_empty() {
|
||||
Some(
|
||||
Span::from(format!(
|
||||
"{empty:<width$}",
|
||||
empty = "-",
|
||||
width = max_branch_width
|
||||
))
|
||||
.dim(),
|
||||
)
|
||||
} else {
|
||||
Some(Span::from(format!("{branch_label:<max_branch_width$}")).cyan())
|
||||
};
|
||||
let cwd_span = if max_cwd_width == 0 {
|
||||
None
|
||||
} else if cwd_label.is_empty() {
|
||||
Some(
|
||||
Span::from(format!(
|
||||
"{empty:<width$}",
|
||||
empty = "-",
|
||||
width = max_cwd_width
|
||||
))
|
||||
.dim(),
|
||||
)
|
||||
} else {
|
||||
Some(Span::from(format!("{cwd_label:<max_cwd_width$}")).dim())
|
||||
};
|
||||
|
||||
let mut preview_width = area.width as usize;
|
||||
preview_width = preview_width.saturating_sub(marker_width);
|
||||
if max_created_width > 0 {
|
||||
preview_width = preview_width.saturating_sub(max_created_width + 2);
|
||||
}
|
||||
if max_updated_width > 0 {
|
||||
preview_width = preview_width.saturating_sub(max_updated_width + 2);
|
||||
}
|
||||
let add_leading_gap = max_created_width == 0 && max_updated_width == 0;
|
||||
if max_branch_width > 0 {
|
||||
preview_width = preview_width.saturating_sub(max_branch_width + 2);
|
||||
}
|
||||
if max_cwd_width > 0 {
|
||||
preview_width = preview_width.saturating_sub(max_cwd_width + 2);
|
||||
}
|
||||
let add_leading_gap = max_updated_width == 0 && max_branch_width == 0 && max_cwd_width == 0;
|
||||
if add_leading_gap {
|
||||
preview_width = preview_width.saturating_sub(2);
|
||||
}
|
||||
let preview = truncate_text(&row.preview, preview_width);
|
||||
let mut spans: Vec<Span> = vec![marker];
|
||||
if let Some(created) = created_span {
|
||||
spans.push(created);
|
||||
spans.push(" ".into());
|
||||
}
|
||||
if let Some(updated) = updated_span {
|
||||
spans.push(updated);
|
||||
spans.push(" ".into());
|
||||
}
|
||||
if let Some(branch) = branch_span {
|
||||
spans.push(branch);
|
||||
spans.push(" ".into());
|
||||
}
|
||||
if let Some(cwd) = cwd_span {
|
||||
spans.push(cwd);
|
||||
spans.push(" ".into());
|
||||
}
|
||||
if add_leading_gap {
|
||||
spans.push(" ".into());
|
||||
}
|
||||
@@ -844,12 +930,6 @@ fn human_time_ago(ts: DateTime<Utc>) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_created_label(row: &Row) -> String {
|
||||
row.created_at
|
||||
.map(human_time_ago)
|
||||
.unwrap_or_else(|| "-".to_string())
|
||||
}
|
||||
|
||||
fn format_updated_label(row: &Row) -> String {
|
||||
match (row.updated_at, row.created_at) {
|
||||
(Some(updated), _) => human_time_ago(updated),
|
||||
@@ -868,15 +948,6 @@ fn render_column_headers(
|
||||
}
|
||||
|
||||
let mut spans: Vec<Span> = vec![" ".into()];
|
||||
if metrics.max_created_width > 0 {
|
||||
let label = format!(
|
||||
"{text:<width$}",
|
||||
text = "Created",
|
||||
width = metrics.max_created_width
|
||||
);
|
||||
spans.push(Span::from(label).bold());
|
||||
spans.push(" ".into());
|
||||
}
|
||||
if metrics.max_updated_width > 0 {
|
||||
let label = format!(
|
||||
"{text:<width$}",
|
||||
@@ -886,32 +957,88 @@ fn render_column_headers(
|
||||
spans.push(Span::from(label).bold());
|
||||
spans.push(" ".into());
|
||||
}
|
||||
if metrics.max_branch_width > 0 {
|
||||
let label = format!(
|
||||
"{text:<width$}",
|
||||
text = "Branch",
|
||||
width = metrics.max_branch_width
|
||||
);
|
||||
spans.push(Span::from(label).bold());
|
||||
spans.push(" ".into());
|
||||
}
|
||||
if metrics.max_cwd_width > 0 {
|
||||
let label = format!(
|
||||
"{text:<width$}",
|
||||
text = "CWD",
|
||||
width = metrics.max_cwd_width
|
||||
);
|
||||
spans.push(Span::from(label).bold());
|
||||
spans.push(" ".into());
|
||||
}
|
||||
spans.push("Conversation".bold());
|
||||
frame.render_widget_ref(Line::from(spans), area);
|
||||
}
|
||||
|
||||
struct ColumnMetrics {
|
||||
max_created_width: usize,
|
||||
max_updated_width: usize,
|
||||
labels: Vec<(String, String)>,
|
||||
max_branch_width: usize,
|
||||
max_cwd_width: usize,
|
||||
labels: Vec<(String, String, String)>,
|
||||
}
|
||||
|
||||
fn calculate_column_metrics(rows: &[Row]) -> ColumnMetrics {
|
||||
let mut labels: Vec<(String, String)> = Vec::with_capacity(rows.len());
|
||||
let mut max_created_width = UnicodeWidthStr::width("Created");
|
||||
fn calculate_column_metrics(rows: &[Row], include_cwd: bool) -> ColumnMetrics {
|
||||
fn right_elide(s: &str, max: usize) -> String {
|
||||
if s.chars().count() <= max {
|
||||
return s.to_string();
|
||||
}
|
||||
if max <= 1 {
|
||||
return "…".to_string();
|
||||
}
|
||||
let tail_len = max - 1;
|
||||
let tail: String = s
|
||||
.chars()
|
||||
.rev()
|
||||
.take(tail_len)
|
||||
.collect::<String>()
|
||||
.chars()
|
||||
.rev()
|
||||
.collect();
|
||||
format!("…{tail}")
|
||||
}
|
||||
|
||||
let mut labels: Vec<(String, String, String)> = Vec::with_capacity(rows.len());
|
||||
let mut max_updated_width = UnicodeWidthStr::width("Updated");
|
||||
let mut max_branch_width = UnicodeWidthStr::width("Branch");
|
||||
let mut max_cwd_width = if include_cwd {
|
||||
UnicodeWidthStr::width("CWD")
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
for row in rows {
|
||||
let created = format_created_label(row);
|
||||
let updated = format_updated_label(row);
|
||||
max_created_width = max_created_width.max(UnicodeWidthStr::width(created.as_str()));
|
||||
let branch_raw = row.git_branch.clone().unwrap_or_default();
|
||||
let branch = right_elide(&branch_raw, 24);
|
||||
let cwd = if include_cwd {
|
||||
let cwd_raw = row
|
||||
.cwd
|
||||
.as_ref()
|
||||
.map(|p| display_path_for(p, std::path::Path::new("/")))
|
||||
.unwrap_or_default();
|
||||
right_elide(&cwd_raw, 24)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
max_updated_width = max_updated_width.max(UnicodeWidthStr::width(updated.as_str()));
|
||||
labels.push((created, updated));
|
||||
max_branch_width = max_branch_width.max(UnicodeWidthStr::width(branch.as_str()));
|
||||
max_cwd_width = max_cwd_width.max(UnicodeWidthStr::width(cwd.as_str()));
|
||||
labels.push((updated, branch, cwd));
|
||||
}
|
||||
|
||||
ColumnMetrics {
|
||||
max_created_width,
|
||||
max_updated_width,
|
||||
max_branch_width,
|
||||
max_cwd_width,
|
||||
labels,
|
||||
}
|
||||
}
|
||||
@@ -1088,6 +1215,8 @@ mod tests {
|
||||
FrameRequester::test_dummy(),
|
||||
loader,
|
||||
String::from("openai"),
|
||||
true,
|
||||
None,
|
||||
);
|
||||
|
||||
let now = Utc::now();
|
||||
@@ -1097,18 +1226,24 @@ mod tests {
|
||||
preview: String::from("Fix resume picker timestamps"),
|
||||
created_at: Some(now - Duration::minutes(16)),
|
||||
updated_at: Some(now - Duration::seconds(42)),
|
||||
cwd: None,
|
||||
git_branch: None,
|
||||
},
|
||||
Row {
|
||||
path: PathBuf::from("/tmp/b.jsonl"),
|
||||
preview: String::from("Investigate lazy pagination cap"),
|
||||
created_at: Some(now - Duration::hours(1)),
|
||||
updated_at: Some(now - Duration::minutes(35)),
|
||||
cwd: None,
|
||||
git_branch: None,
|
||||
},
|
||||
Row {
|
||||
path: PathBuf::from("/tmp/c.jsonl"),
|
||||
preview: String::from("Explain the codebase"),
|
||||
created_at: Some(now - Duration::hours(2)),
|
||||
updated_at: Some(now - Duration::hours(2)),
|
||||
cwd: None,
|
||||
git_branch: None,
|
||||
},
|
||||
];
|
||||
state.all_rows = rows.clone();
|
||||
@@ -1118,7 +1253,7 @@ mod tests {
|
||||
state.scroll_top = 0;
|
||||
state.update_view_rows(3);
|
||||
|
||||
let metrics = calculate_column_metrics(&state.filtered_rows);
|
||||
let metrics = calculate_column_metrics(&state.filtered_rows, state.show_all);
|
||||
|
||||
let width: u16 = 80;
|
||||
let height: u16 = 6;
|
||||
@@ -1148,6 +1283,8 @@ mod tests {
|
||||
FrameRequester::test_dummy(),
|
||||
loader,
|
||||
String::from("openai"),
|
||||
true,
|
||||
None,
|
||||
);
|
||||
|
||||
state.reset_pagination();
|
||||
@@ -1214,6 +1351,8 @@ mod tests {
|
||||
FrameRequester::test_dummy(),
|
||||
loader,
|
||||
String::from("openai"),
|
||||
true,
|
||||
None,
|
||||
);
|
||||
state.reset_pagination();
|
||||
state.ingest_page(page(
|
||||
@@ -1243,6 +1382,8 @@ mod tests {
|
||||
FrameRequester::test_dummy(),
|
||||
loader,
|
||||
String::from("openai"),
|
||||
true,
|
||||
None,
|
||||
);
|
||||
|
||||
let mut items = Vec::new();
|
||||
@@ -1291,6 +1432,8 @@ mod tests {
|
||||
FrameRequester::test_dummy(),
|
||||
loader,
|
||||
String::from("openai"),
|
||||
true,
|
||||
None,
|
||||
);
|
||||
|
||||
let mut items = Vec::new();
|
||||
@@ -1335,6 +1478,8 @@ mod tests {
|
||||
FrameRequester::test_dummy(),
|
||||
loader,
|
||||
String::from("openai"),
|
||||
true,
|
||||
None,
|
||||
);
|
||||
state.reset_pagination();
|
||||
state.ingest_page(page(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
source: tui/src/resume_picker.rs
|
||||
expression: snapshot
|
||||
---
|
||||
Created Updated Conversation
|
||||
16 minutes ago 42 seconds ago Fix resume picker timestamps
|
||||
> 1 hour ago 35 minutes ago Investigate lazy pagination cap
|
||||
2 hours ago 2 hours ago Explain the codebase
|
||||
Updated Branch CWD Conversation
|
||||
42 seconds ago - - Fix resume picker timestamps
|
||||
> 35 minutes ago - - Investigate lazy pagination cap
|
||||
2 hours ago - - Explain the codebase
|
||||
|
||||
@@ -21,6 +21,7 @@ Key flags: `--model/-m`, `--ask-for-approval/-a`.
|
||||
- Run `codex resume` to display the session picker UI
|
||||
- Resume most recent: `codex resume --last`
|
||||
- Resume by id: `codex resume <SESSION_ID>` (You can get session ids from /status or `~/.codex/sessions/`)
|
||||
- The picker shows the session's original working directory and, when available, the Git branch it was recorded on
|
||||
|
||||
Examples:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user