Skip to content

Commit 84a0ba9

Browse files
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" />
1 parent 4a5d6f7 commit 84a0ba9

File tree

6 files changed

+43
-10
lines changed

6 files changed

+43
-10
lines changed

codex-rs/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/cli/src/main.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,11 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
177177
root_config_overrides.clone(),
178178
);
179179
let usage = codex_tui::run_main(interactive, codex_linux_sandbox_exe).await?;
180-
if !usage.is_zero() {
181-
println!("{}", codex_core::protocol::FinalOutput::from(usage));
180+
if !usage.token_usage.is_zero() {
181+
println!(
182+
"{}",
183+
codex_core::protocol::FinalOutput::from(usage.token_usage)
184+
);
182185
}
183186
}
184187
Some(Subcommand::Exec(mut exec_cli)) => {

codex-rs/tui/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ unicode-segmentation = "1.12.0"
8585
unicode-width = "0.1"
8686
url = "2"
8787
pathdiff = "0.2"
88+
owo-colors = "4.2.0"
8889

8990
[target.'cfg(unix)'.dependencies]
9091
libc = "0.2"

codex-rs/tui/src/app.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use codex_core::config::persist_model_selection;
1515
use codex_core::model_family::find_family_for_model;
1616
use codex_core::protocol::TokenUsage;
1717
use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig;
18+
use codex_protocol::mcp_protocol::ConversationId;
1819
use color_eyre::eyre::Result;
1920
use color_eyre::eyre::WrapErr;
2021
use crossterm::event::KeyCode;
@@ -33,6 +34,12 @@ use tokio::select;
3334
use tokio::sync::mpsc::unbounded_channel;
3435
// use uuid::Uuid;
3536

37+
#[derive(Debug, Clone)]
38+
pub struct AppExitInfo {
39+
pub token_usage: TokenUsage,
40+
pub conversation_id: Option<ConversationId>,
41+
}
42+
3643
pub(crate) struct App {
3744
pub(crate) server: Arc<ConversationManager>,
3845
pub(crate) app_event_tx: AppEventSender,
@@ -70,7 +77,7 @@ impl App {
7077
initial_prompt: Option<String>,
7178
initial_images: Vec<PathBuf>,
7279
resume_selection: ResumeSelection,
73-
) -> Result<TokenUsage> {
80+
) -> Result<AppExitInfo> {
7481
use tokio_stream::StreamExt;
7582
let (app_event_tx, mut app_event_rx) = unbounded_channel();
7683
let app_event_tx = AppEventSender::new(app_event_tx);
@@ -153,7 +160,10 @@ impl App {
153160
}
154161
} {}
155162
tui.terminal.clear()?;
156-
Ok(app.token_usage())
163+
Ok(AppExitInfo {
164+
token_usage: app.token_usage(),
165+
conversation_id: app.chat_widget.conversation_id(),
166+
})
157167
}
158168

159169
pub(crate) async fn handle_tui_event(

codex-rs/tui/src/lib.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#![deny(clippy::print_stdout, clippy::print_stderr)]
55
#![deny(clippy::disallowed_methods)]
66
use app::App;
7+
pub use app::AppExitInfo;
78
use codex_core::AuthManager;
89
use codex_core::BUILT_IN_OSS_MODEL_PROVIDER_ID;
910
use codex_core::CodexAuth;
@@ -86,7 +87,7 @@ use codex_core::internal_storage::InternalStorage;
8687
pub async fn run_main(
8788
cli: Cli,
8889
codex_linux_sandbox_exe: Option<PathBuf>,
89-
) -> std::io::Result<codex_core::protocol::TokenUsage> {
90+
) -> std::io::Result<AppExitInfo> {
9091
let (sandbox_mode, approval_policy) = if cli.full_auto {
9192
(
9293
Some(SandboxMode::WorkspaceWrite),
@@ -258,7 +259,7 @@ async fn run_ratatui_app(
258259
mut internal_storage: InternalStorage,
259260
active_profile: Option<String>,
260261
should_show_trust_screen: bool,
261-
) -> color_eyre::Result<codex_core::protocol::TokenUsage> {
262+
) -> color_eyre::Result<AppExitInfo> {
262263
let mut config = config;
263264
color_eyre::install()?;
264265

@@ -370,7 +371,10 @@ async fn run_ratatui_app(
370371
resume_picker::ResumeSelection::Exit => {
371372
restore();
372373
session_log::log_session_end();
373-
return Ok(codex_core::protocol::TokenUsage::default());
374+
return Ok(AppExitInfo {
375+
token_usage: codex_core::protocol::TokenUsage::default(),
376+
conversation_id: None,
377+
});
374378
}
375379
other => other,
376380
}

codex-rs/tui/src/main.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use codex_arg0::arg0_dispatch_or_else;
33
use codex_common::CliConfigOverrides;
44
use codex_tui::Cli;
55
use codex_tui::run_main;
6+
use owo_colors::OwoColorize;
7+
use supports_color::Stream;
68

79
#[derive(Parser, Debug)]
810
struct TopCli {
@@ -21,9 +23,21 @@ fn main() -> anyhow::Result<()> {
2123
.config_overrides
2224
.raw_overrides
2325
.splice(0..0, top_cli.config_overrides.raw_overrides);
24-
let usage = run_main(inner, codex_linux_sandbox_exe).await?;
25-
if !usage.is_zero() {
26-
println!("{}", codex_core::protocol::FinalOutput::from(usage));
26+
let exit_info = run_main(inner, codex_linux_sandbox_exe).await?;
27+
let token_usage = exit_info.token_usage;
28+
let conversation_id = exit_info.conversation_id;
29+
if !token_usage.is_zero() {
30+
println!("{}", codex_core::protocol::FinalOutput::from(token_usage),);
31+
if let Some(session_id) = conversation_id {
32+
let command = format!("codex resume {session_id}");
33+
let prefix = "To continue this session, run ";
34+
let suffix = ".";
35+
if supports_color::on(Stream::Stdout).is_some() {
36+
println!("{}{}{}", prefix, command.cyan(), suffix);
37+
} else {
38+
println!("{prefix}{command}{suffix}");
39+
}
40+
}
2741
}
2842
Ok(())
2943
})

0 commit comments

Comments
 (0)