fix(cli/resume): restore coder resume support

Closes #214.
This commit is contained in:
James Peter
2025-09-22 12:19:12 +10:00
parent 6716de57b7
commit b46786d36f
5 changed files with 153 additions and 9 deletions
+97 -4
View File
@@ -1,3 +1,5 @@
use anyhow::anyhow;
use anyhow::Context;
use clap::CommandFactory;
use clap::Parser;
use clap_complete::Shell;
@@ -15,9 +17,14 @@ use codex_cli::proto;
mod llm;
use llm::{LlmCli, run_llm};
use codex_common::CliConfigOverrides;
use codex_core::find_conversation_path_by_id_str;
use codex_core::RolloutRecorder;
use codex_exec::Cli as ExecCli;
use codex_tui::Cli as TuiCli;
use std::path::Path;
use std::path::PathBuf;
use std::process;
use tokio::runtime::Builder as TokioRuntimeBuilder;
mod mcp_cmd;
@@ -211,6 +218,8 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
subcommand,
} = MultitoolCli::parse();
interactive.finalize_defaults();
match subcommand {
None => {
prepend_config_flags(
@@ -237,8 +246,9 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
Some(Subcommand::Resume(ResumeCommand {
session_id,
last,
config_overrides,
mut config_overrides,
})) => {
config_overrides.finalize_defaults();
interactive = finalize_resume_interactive(
interactive,
root_config_overrides.clone(),
@@ -353,16 +363,24 @@ fn prepend_config_flags(
fn finalize_resume_interactive(
mut interactive: TuiCli,
root_config_overrides: CliConfigOverrides,
_session_id: Option<String>,
_last: bool,
resume_cli: TuiCli,
session_id: Option<String>,
last: bool,
mut resume_cli: TuiCli,
) -> TuiCli {
// Our fork does not expose explicit resume fields on the TUI CLI.
// We simply merge resume-scoped flags and root overrides and run the TUI.
interactive.finalize_defaults();
resume_cli.finalize_defaults();
// Merge resume-scoped flags and overrides with highest precedence.
merge_resume_cli_flags(&mut interactive, resume_cli);
if let Err(err) = apply_resume_directives(&mut interactive, session_id, last) {
eprintln!("{}", err);
process::exit(1);
}
// Propagate any root-level config overrides (e.g. `-c key=value`).
prepend_config_flags(&mut interactive.config_overrides, root_config_overrides);
@@ -404,12 +422,87 @@ fn merge_resume_cli_flags(interactive: &mut TuiCli, resume_cli: TuiCli) {
interactive.prompt = Some(prompt);
}
if resume_cli.enable_web_search || resume_cli.disable_web_search {
interactive.enable_web_search = resume_cli.enable_web_search;
interactive.disable_web_search = resume_cli.disable_web_search;
interactive.web_search = resume_cli.web_search;
}
interactive
.config_overrides
.raw_overrides
.extend(resume_cli.config_overrides.raw_overrides);
}
fn apply_resume_directives(
interactive: &mut TuiCli,
session_id: Option<String>,
last: bool,
) -> anyhow::Result<()> {
interactive.resume_picker = false;
interactive.resume_last = false;
interactive.resume_session_id = None;
match (session_id, last) {
(Some(id), _) => {
let path = resolve_resume_path(Some(id.as_str()), false)?
.ok_or_else(|| anyhow!("No recorded session found with id {id}"))?;
interactive.resume_session_id = Some(id);
push_experimental_resume_override(interactive, &path);
}
(None, true) => {
let path = resolve_resume_path(None, true)?
.ok_or_else(|| anyhow!("No recent sessions found to resume. Start a session with `code` first."))?;
interactive.resume_last = true;
push_experimental_resume_override(interactive, &path);
}
(None, false) => {
interactive.resume_picker = true;
}
}
Ok(())
}
fn resolve_resume_path(session_id: Option<&str>, last: bool) -> anyhow::Result<Option<PathBuf>> {
if session_id.is_none() && !last {
return Ok(None);
}
let codex_home = codex_core::config::find_codex_home()
.context("failed to locate Codex home directory")?;
let runtime = TokioRuntimeBuilder::new_current_thread()
.enable_all()
.build()
.context("failed to create async runtime for resume lookup")?;
runtime.block_on(async {
if let Some(id) = session_id {
let maybe = find_conversation_path_by_id_str(&codex_home, id)
.await
.context("failed to look up session by id")?;
Ok(maybe)
} else if last {
let page = RolloutRecorder::list_conversations(&codex_home, 1, None)
.await
.context("failed to list recorded sessions")?;
Ok(page.items.first().map(|it| it.path.clone()))
} else {
Ok(None)
}
})
}
fn push_experimental_resume_override(interactive: &mut TuiCli, path: &Path) {
let raw = path.to_string_lossy();
let escaped = raw.replace('\\', "\\\\").replace('"', "\\\"");
interactive
.config_overrides
.raw_overrides
.push(format!("experimental_resume=\"{escaped}\""));
}
fn print_completion(cmd: CompletionCommand) {
let mut app = MultitoolCli::command();
let name = "code";
+10
View File
@@ -152,6 +152,7 @@ pub(crate) struct ChatWidgetArgs {
terminal_info: TerminalInfo,
show_order_overlay: bool,
enable_perf: bool,
resume_picker: bool,
}
impl App<'_> {
@@ -166,6 +167,7 @@ impl App<'_> {
show_order_overlay: bool,
terminal_info: TerminalInfo,
enable_perf: bool,
resume_picker: bool,
startup_footer_notice: Option<String>,
) -> Self {
let conversation_manager = Arc::new(ConversationManager::new(AuthManager::shared(
@@ -273,6 +275,7 @@ impl App<'_> {
terminal_info: terminal_info.clone(),
show_order_overlay,
enable_perf,
resume_picker,
};
AppState::Onboarding {
screen: OnboardingScreen::new(OnboardingScreenArgs {
@@ -296,6 +299,9 @@ impl App<'_> {
show_order_overlay,
);
chat_widget.enable_perf(enable_perf);
if resume_picker {
chat_widget.show_resume_picker();
}
// Check for initial animations after widget is created
chat_widget.check_for_initial_animations();
if let Some(notice) = startup_footer_notice {
@@ -1668,6 +1674,7 @@ impl App<'_> {
terminal_info,
show_order_overlay,
enable_perf,
resume_picker,
}) => {
let mut w = ChatWidget::new(
config,
@@ -1679,6 +1686,9 @@ impl App<'_> {
show_order_overlay,
);
w.enable_perf(enable_perf);
if resume_picker {
w.show_resume_picker();
}
self.app_state = AppState::Chat { widget: Box::new(w) };
self.terminal_runs.clear();
}
+37 -2
View File
@@ -1,3 +1,4 @@
use clap::ArgAction;
use clap::Parser;
use codex_common::ApprovalModeCliArg;
use codex_common::CliConfigOverrides;
@@ -60,8 +61,18 @@ pub struct Cli {
#[clap(long = "cd", short = 'C', value_name = "DIR")]
pub cwd: Option<PathBuf>,
// Web search is now enabled by default via an internal override.
// Intentionally not exposed as a command-line option.
/// Enable web search support. Enabled by default; use --no-search to disable.
#[arg(long = "search", action = ArgAction::SetTrue)]
pub enable_web_search: bool,
/// Disable web search support explicitly.
#[arg(long = "no-search", action = ArgAction::SetTrue, hide = true)]
pub disable_web_search: bool,
/// Effective web search toggle after applying flags.
#[clap(skip)]
pub web_search: bool,
/// Enable debug logging of all LLM requests and responses to files.
#[clap(long = "debug", short = 'd', default_value_t = false)]
pub debug: bool,
@@ -79,4 +90,28 @@ pub struct Cli {
#[clap(skip)]
pub config_overrides: CliConfigOverrides,
/// Start in resume picker mode when true (used by `code resume`).
#[clap(skip)]
pub resume_picker: bool,
/// Resume the most recent session automatically when true.
#[clap(skip)]
pub resume_last: bool,
/// Resume a specific session id when provided.
#[clap(skip)]
pub resume_session_id: Option<String>,
}
impl Cli {
pub fn finalize_defaults(&mut self) {
self.web_search = if self.disable_web_search {
false
} else if self.enable_web_search {
true
} else {
true
};
}
}
+8 -3
View File
@@ -97,9 +97,11 @@ pub use cli::Cli;
// (tests access modules directly within the crate)
pub async fn run_main(
cli: Cli,
mut cli: Cli,
codex_linux_sandbox_exe: Option<PathBuf>,
) -> std::io::Result<codex_core::protocol::TokenUsage> {
cli.finalize_defaults();
let (sandbox_mode, approval_policy) = if cli.full_auto {
(
Some(SandboxMode::WorkspaceWrite),
@@ -153,8 +155,7 @@ pub async fn run_main(
disable_response_storage: cli.oss.then_some(true),
show_raw_agent_reasoning: cli.oss.then_some(true),
debug: Some(cli.debug),
// Enable web search by default (no CLI flag).
tools_web_search_request: Some(true),
tools_web_search_request: Some(cli.web_search),
};
// Parse `-c` overrides from the CLI.
@@ -388,6 +389,9 @@ fn run_ratatui_app(
debug,
order,
timing,
resume_picker,
resume_last: _,
resume_session_id: _,
..
} = cli;
let mut app = App::new(
@@ -399,6 +403,7 @@ fn run_ratatui_app(
order,
terminal_info,
timing,
resume_picker,
startup_footer_notice,
);
+1
View File
@@ -17,6 +17,7 @@ fn main() -> anyhow::Result<()> {
arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
let top_cli = TopCli::parse();
let mut inner = top_cli.inner;
inner.finalize_defaults();
inner
.config_overrides
.raw_overrides