hint for codex resume on tui exit (#3757)

<img width="931" height="438" alt="Screenshot 2025-09-16 at 4 25 19 PM"
src="https://github.com/user-attachments/assets/ccfb8df1-feaf-45b4-8f7f-56100de916d5"
/>
This commit is contained in:
Jeremy Rose
2025-09-18 09:28:32 -07:00
committed by GitHub
parent 4a5d6f7c71
commit 84a0ba9bf5
6 changed files with 43 additions and 10 deletions

1
codex-rs/Cargo.lock generated
View File

@@ -929,6 +929,7 @@ dependencies = [
"libc", "libc",
"mcp-types", "mcp-types",
"once_cell", "once_cell",
"owo-colors",
"path-clean", "path-clean",
"pathdiff", "pathdiff",
"pretty_assertions", "pretty_assertions",

View File

@@ -177,8 +177,11 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
root_config_overrides.clone(), root_config_overrides.clone(),
); );
let usage = codex_tui::run_main(interactive, codex_linux_sandbox_exe).await?; let usage = codex_tui::run_main(interactive, codex_linux_sandbox_exe).await?;
if !usage.is_zero() { if !usage.token_usage.is_zero() {
println!("{}", codex_core::protocol::FinalOutput::from(usage)); println!(
"{}",
codex_core::protocol::FinalOutput::from(usage.token_usage)
);
} }
} }
Some(Subcommand::Exec(mut exec_cli)) => { Some(Subcommand::Exec(mut exec_cli)) => {

View File

@@ -85,6 +85,7 @@ unicode-segmentation = "1.12.0"
unicode-width = "0.1" unicode-width = "0.1"
url = "2" url = "2"
pathdiff = "0.2" pathdiff = "0.2"
owo-colors = "4.2.0"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2" libc = "0.2"

View File

@@ -15,6 +15,7 @@ use codex_core::config::persist_model_selection;
use codex_core::model_family::find_family_for_model; use codex_core::model_family::find_family_for_model;
use codex_core::protocol::TokenUsage; use codex_core::protocol::TokenUsage;
use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig; use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig;
use codex_protocol::mcp_protocol::ConversationId;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use color_eyre::eyre::WrapErr; use color_eyre::eyre::WrapErr;
use crossterm::event::KeyCode; use crossterm::event::KeyCode;
@@ -33,6 +34,12 @@ use tokio::select;
use tokio::sync::mpsc::unbounded_channel; use tokio::sync::mpsc::unbounded_channel;
// use uuid::Uuid; // use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct AppExitInfo {
pub token_usage: TokenUsage,
pub conversation_id: Option<ConversationId>,
}
pub(crate) struct App { pub(crate) struct App {
pub(crate) server: Arc<ConversationManager>, pub(crate) server: Arc<ConversationManager>,
pub(crate) app_event_tx: AppEventSender, pub(crate) app_event_tx: AppEventSender,
@@ -70,7 +77,7 @@ impl App {
initial_prompt: Option<String>, initial_prompt: Option<String>,
initial_images: Vec<PathBuf>, initial_images: Vec<PathBuf>,
resume_selection: ResumeSelection, resume_selection: ResumeSelection,
) -> Result<TokenUsage> { ) -> Result<AppExitInfo> {
use tokio_stream::StreamExt; use tokio_stream::StreamExt;
let (app_event_tx, mut app_event_rx) = unbounded_channel(); let (app_event_tx, mut app_event_rx) = unbounded_channel();
let app_event_tx = AppEventSender::new(app_event_tx); let app_event_tx = AppEventSender::new(app_event_tx);
@@ -153,7 +160,10 @@ impl App {
} }
} {} } {}
tui.terminal.clear()?; tui.terminal.clear()?;
Ok(app.token_usage()) Ok(AppExitInfo {
token_usage: app.token_usage(),
conversation_id: app.chat_widget.conversation_id(),
})
} }
pub(crate) async fn handle_tui_event( pub(crate) async fn handle_tui_event(

View File

@@ -4,6 +4,7 @@
#![deny(clippy::print_stdout, clippy::print_stderr)] #![deny(clippy::print_stdout, clippy::print_stderr)]
#![deny(clippy::disallowed_methods)] #![deny(clippy::disallowed_methods)]
use app::App; use app::App;
pub use app::AppExitInfo;
use codex_core::AuthManager; use codex_core::AuthManager;
use codex_core::BUILT_IN_OSS_MODEL_PROVIDER_ID; use codex_core::BUILT_IN_OSS_MODEL_PROVIDER_ID;
use codex_core::CodexAuth; use codex_core::CodexAuth;
@@ -86,7 +87,7 @@ use codex_core::internal_storage::InternalStorage;
pub async fn run_main( pub async fn run_main(
cli: Cli, cli: Cli,
codex_linux_sandbox_exe: Option<PathBuf>, codex_linux_sandbox_exe: Option<PathBuf>,
) -> std::io::Result<codex_core::protocol::TokenUsage> { ) -> std::io::Result<AppExitInfo> {
let (sandbox_mode, approval_policy) = if cli.full_auto { let (sandbox_mode, approval_policy) = if cli.full_auto {
( (
Some(SandboxMode::WorkspaceWrite), Some(SandboxMode::WorkspaceWrite),
@@ -258,7 +259,7 @@ async fn run_ratatui_app(
mut internal_storage: InternalStorage, mut internal_storage: InternalStorage,
active_profile: Option<String>, active_profile: Option<String>,
should_show_trust_screen: bool, should_show_trust_screen: bool,
) -> color_eyre::Result<codex_core::protocol::TokenUsage> { ) -> color_eyre::Result<AppExitInfo> {
let mut config = config; let mut config = config;
color_eyre::install()?; color_eyre::install()?;
@@ -370,7 +371,10 @@ async fn run_ratatui_app(
resume_picker::ResumeSelection::Exit => { resume_picker::ResumeSelection::Exit => {
restore(); restore();
session_log::log_session_end(); session_log::log_session_end();
return Ok(codex_core::protocol::TokenUsage::default()); return Ok(AppExitInfo {
token_usage: codex_core::protocol::TokenUsage::default(),
conversation_id: None,
});
} }
other => other, other => other,
} }

View File

@@ -3,6 +3,8 @@ use codex_arg0::arg0_dispatch_or_else;
use codex_common::CliConfigOverrides; use codex_common::CliConfigOverrides;
use codex_tui::Cli; use codex_tui::Cli;
use codex_tui::run_main; use codex_tui::run_main;
use owo_colors::OwoColorize;
use supports_color::Stream;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct TopCli { struct TopCli {
@@ -21,9 +23,21 @@ fn main() -> anyhow::Result<()> {
.config_overrides .config_overrides
.raw_overrides .raw_overrides
.splice(0..0, top_cli.config_overrides.raw_overrides); .splice(0..0, top_cli.config_overrides.raw_overrides);
let usage = run_main(inner, codex_linux_sandbox_exe).await?; let exit_info = run_main(inner, codex_linux_sandbox_exe).await?;
if !usage.is_zero() { let token_usage = exit_info.token_usage;
println!("{}", codex_core::protocol::FinalOutput::from(usage)); let conversation_id = exit_info.conversation_id;
if !token_usage.is_zero() {
println!("{}", codex_core::protocol::FinalOutput::from(token_usage),);
if let Some(session_id) = conversation_id {
let command = format!("codex resume {session_id}");
let prefix = "To continue this session, run ";
let suffix = ".";
if supports_color::on(Stream::Stdout).is_some() {
println!("{}{}{}", prefix, command.cyan(), suffix);
} else {
println!("{prefix}{command}{suffix}");
}
}
} }
Ok(()) Ok(())
}) })