diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 1b84dda0e1..b6a9e9819d 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -298,6 +298,17 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "atomic-waker" version = "1.1.2" diff --git a/codex-rs/cli/src/login.rs b/codex-rs/cli/src/login.rs index b8a8e45e02..cff03e7c16 100644 --- a/codex-rs/cli/src/login.rs +++ b/codex-rs/cli/src/login.rs @@ -12,8 +12,8 @@ use codex_protocol::mcp_protocol::AuthMode; use std::env; use std::path::PathBuf; -pub async fn login_with_chatgpt(codex_home: PathBuf) -> std::io::Result<()> { - let opts = ServerOptions::new(codex_home, CLIENT_ID.to_string()); +pub async fn login_with_chatgpt(codex_home: PathBuf, originator: String) -> std::io::Result<()> { + let opts = ServerOptions::new(codex_home, CLIENT_ID.to_string(), originator); let server = run_login_server(opts)?; eprintln!( @@ -27,7 +27,12 @@ pub async fn login_with_chatgpt(codex_home: PathBuf) -> std::io::Result<()> { pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) -> ! { let config = load_config_or_exit(cli_config_overrides); - match login_with_chatgpt(config.codex_home).await { + match login_with_chatgpt( + config.codex_home, + config.responses_originator_header.clone(), + ) + .await + { Ok(_) => { eprintln!("Successfully logged in"); std::process::exit(0); diff --git a/codex-rs/cloud-tasks/src/app.rs b/codex-rs/cloud-tasks/src/app.rs index 7eba606b24..b8ec813fff 100644 --- a/codex-rs/cloud-tasks/src/app.rs +++ b/codex-rs/cloud-tasks/src/app.rs @@ -34,12 +34,9 @@ pub struct ApplyModalState { use crate::scrollable_diff::ScrollableDiff; use codex_cloud_tasks_api::CloudBackend; -use codex_cloud_tasks_api::DiffSummary; use codex_cloud_tasks_api::TaskId; use codex_cloud_tasks_api::TaskSummary; use throbber_widgets_tui::ThrobberState; -use tokio::sync::mpsc::UnboundedReceiver; -use tokio::sync::mpsc::UnboundedSender; #[derive(Default)] pub struct App { @@ -65,8 +62,7 @@ pub struct App { // Background enrichment coordination pub list_generation: u64, pub in_flight: std::collections::HashSet, - pub summary_cache: std::collections::HashMap, - pub no_diff_yet: std::collections::HashSet, + // Background enrichment caches were planned; currently unused. } impl App { @@ -90,8 +86,6 @@ impl App { apply_preflight_inflight: false, list_generation: 0, in_flight: std::collections::HashSet::new(), - summary_cache: std::collections::HashMap::new(), - no_diff_yet: std::collections::HashSet::new(), } } @@ -136,14 +130,7 @@ pub enum AppEvent { env: Option, result: anyhow::Result>, }, - /// Background diff summary computed for a task (or determined absent) - TaskSummaryUpdated { - generation: u64, - id: TaskId, - summary: DiffSummary, - no_diff_yet: bool, - environment_id: Option, - }, + // Background diff summary events were planned; removed for now to keep code minimal. /// Autodetection of a likely environment id finished EnvironmentAutodetected(anyhow::Result), /// Background completion of environment list fetch @@ -176,9 +163,7 @@ pub enum AppEvent { }, } -pub type AppEventTx = UnboundedSender; -pub type AppEventRx = UnboundedReceiver; - +// Convenience aliases; currently unused. #[cfg(test)] mod tests { use super::*; diff --git a/codex-rs/cloud-tasks/src/bin/detailcheck.rs b/codex-rs/cloud-tasks/src/bin/detailcheck.rs index 3672a19c17..a392b55f68 100644 --- a/codex-rs/cloud-tasks/src/bin/detailcheck.rs +++ b/codex-rs/cloud-tasks/src/bin/detailcheck.rs @@ -19,10 +19,10 @@ async fn main() -> anyhow::Result<()> { AuthMode::ChatGPT, "codex_cloud_tasks_detailcheck".to_string(), ); - if let Some(auth) = am.auth() { - if let Ok(tok) = auth.get_token().await { - client = client.with_bearer_token(tok); - } + if let Some(auth) = am.auth() + && let Ok(tok) = auth.get_token().await + { + client = client.with_bearer_token(tok); } } diff --git a/codex-rs/cloud-tasks/src/bin/envcheck.rs b/codex-rs/cloud-tasks/src/bin/envcheck.rs index 2a906b3015..5cec6d7b6d 100644 --- a/codex-rs/cloud-tasks/src/bin/envcheck.rs +++ b/codex-rs/cloud-tasks/src/bin/envcheck.rs @@ -77,10 +77,10 @@ async fn main() -> anyhow::Result<()> { .or_else(|| extract_chatgpt_account_id(&token)) { println!("auth: ChatGPT-Account-Id: {account_id}"); - if let Ok(name) = HeaderName::from_bytes(b"ChatGPT-Account-Id") { - if let Ok(hv) = HeaderValue::from_str(&account_id) { - headers.insert(name, hv); - } + if let Ok(name) = HeaderName::from_bytes(b"ChatGPT-Account-Id") + && let Ok(hv) = HeaderValue::from_str(&account_id) + { + headers.insert(name, hv); } } } diff --git a/codex-rs/cloud-tasks/src/bin/newtask.rs b/codex-rs/cloud-tasks/src/bin/newtask.rs index 65e01c72e8..d36bc46521 100644 --- a/codex-rs/cloud-tasks/src/bin/newtask.rs +++ b/codex-rs/cloud-tasks/src/bin/newtask.rs @@ -82,10 +82,10 @@ async fn main() -> anyhow::Result<()> { .or_else(|| extract_chatgpt_account_id(&token)) { println!("auth: ChatGPT-Account-Id: {account_id}"); - if let Ok(name) = HeaderName::from_bytes(b"ChatGPT-Account-Id") { - if let Ok(hv) = HeaderValue::from_str(&account_id) { - headers.insert(name, hv); - } + if let Ok(name) = HeaderName::from_bytes(b"ChatGPT-Account-Id") + && let Ok(hv) = HeaderValue::from_str(&account_id) + { + headers.insert(name, hv); } } } @@ -141,13 +141,13 @@ async fn main() -> anyhow::Result<()> { })); // Optional: starting diff via env var for quick testing - if let Ok(diff) = std::env::var("CODEX_STARTING_DIFF") { - if !diff.is_empty() { - input_items.push(serde_json::json!({ - "type": "pre_apply_patch", - "output_diff": { "diff": diff } - })); - } + if let Ok(diff) = std::env::var("CODEX_STARTING_DIFF") + && !diff.is_empty() + { + input_items.push(serde_json::json!({ + "type": "pre_apply_patch", + "output_diff": { "diff": diff } + })); } let request_body = serde_json::json!({ diff --git a/codex-rs/cloud-tasks/src/env_detect.rs b/codex-rs/cloud-tasks/src/env_detect.rs index 24b10d8e64..b575a7da2b 100644 --- a/codex-rs/cloud-tasks/src/env_detect.rs +++ b/codex-rs/cloud-tasks/src/env_detect.rs @@ -174,38 +174,38 @@ fn get_git_origins() -> Vec { let out = std::process::Command::new("git") .args(["config", "--get-regexp", "remote\\..*\\.url"]) .output(); - if let Ok(ok) = out { - if ok.status.success() { - let s = String::from_utf8_lossy(&ok.stdout); - let mut urls = Vec::new(); - for line in s.lines() { - if let Some((_, url)) = line.split_once(' ') { - urls.push(url.trim().to_string()); - } - } - if !urls.is_empty() { - return uniq(urls); + if let Ok(ok) = out + && ok.status.success() + { + let s = String::from_utf8_lossy(&ok.stdout); + let mut urls = Vec::new(); + for line in s.lines() { + if let Some((_, url)) = line.split_once(' ') { + urls.push(url.trim().to_string()); } } + if !urls.is_empty() { + return uniq(urls); + } } // Fallback: git remote -v let out = std::process::Command::new("git") .args(["remote", "-v"]) .output(); - if let Ok(ok) = out { - if ok.status.success() { - let s = String::from_utf8_lossy(&ok.stdout); - let mut urls = Vec::new(); - for line in s.lines() { - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() >= 2 { - urls.push(parts[1].to_string()); - } - } - if !urls.is_empty() { - return uniq(urls); + if let Ok(ok) = out + && ok.status.success() + { + let s = String::from_utf8_lossy(&ok.stdout); + let mut urls = Vec::new(); + for line in s.lines() { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 2 { + urls.push(parts[1].to_string()); } } + if !urls.is_empty() { + return uniq(urls); + } } Vec::new() } diff --git a/codex-rs/cloud-tasks/src/lib.rs b/codex-rs/cloud-tasks/src/lib.rs index 1f3b178653..cf768cd652 100644 --- a/codex-rs/cloud-tasks/src/lib.rs +++ b/codex-rs/cloud-tasks/src/lib.rs @@ -29,47 +29,7 @@ pub(crate) fn append_error_log(message: impl AsRef) { } } -/// Summarize a unified or codex-style patch into counts for UI display. -fn summarize_diff(patch: &str) -> codex_cloud_tasks_api::DiffSummary { - // Count +/- lines via simple prefix scan (skip headers) - let (mut adds, mut dels) = (0usize, 0usize); - for line in patch.lines() { - if line.starts_with("+++") || line.starts_with("---") || line.starts_with("@@") { - continue; - } - match line.as_bytes().first().copied() { - Some(b'+') => adds += 1, - Some(b'-') => dels += 1, - _ => {} - } - } - - // Count files: prefer codex patch file ops, else git diff headers, else infer 1 if any changes. - let mut files = 0usize; - for line in patch.lines() { - if line.starts_with("*** Add File:") - || line.starts_with("*** Update File:") - || line.starts_with("*** Delete File:") - { - files += 1; - } - } - if files == 0 { - files = patch - .lines() - .filter(|l| l.starts_with("diff --git ")) - .count(); - } - if files == 0 && (adds > 0 || dels > 0) { - files = 1; - } - - codex_cloud_tasks_api::DiffSummary { - files_changed: files, - lines_added: adds, - lines_removed: dels, - } -} +// (no standalone patch summarizer needed – UI displays raw diffs) /// Entry point for the `codex cloud-tasks` subcommand. pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> anyhow::Result<()> { @@ -211,7 +171,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a // New list generation; reset background enrichment coordination app.list_generation = app.list_generation.saturating_add(1); app.in_flight.clear(); - app.no_diff_yet.clear(); + // reset any in-flight enrichment state // Event stream use crossterm::event::Event; @@ -266,21 +226,22 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a codex_login::AuthMode::ChatGPT, "codex_cloud_tasks_tui".to_string(), ); - if let Some(auth) = am.auth() { - if let Ok(tok) = auth.get_token().await && !tok.is_empty() { - let v = format!("Bearer {tok}"); - if let Ok(hv) = reqwest::header::HeaderValue::from_str(&v) { - headers.insert(reqwest::header::AUTHORIZATION, hv); - } - if let Some(acc) = auth - .get_account_id() - .or_else(|| extract_chatgpt_account_id(&tok)) - && let Ok(name) = - reqwest::header::HeaderName::from_bytes(b"ChatGPT-Account-Id") - && let Ok(hv) = reqwest::header::HeaderValue::from_str(&acc) - { - headers.insert(name, hv); - } + if let Some(auth) = am.auth() + && let Ok(tok) = auth.get_token().await + && !tok.is_empty() + { + let v = format!("Bearer {tok}"); + if let Ok(hv) = reqwest::header::HeaderValue::from_str(&v) { + headers.insert(reqwest::header::AUTHORIZATION, hv); + } + if let Some(acc) = auth + .get_account_id() + .or_else(|| extract_chatgpt_account_id(&tok)) + && let Ok(name) = + reqwest::header::HeaderName::from_bytes(b"ChatGPT-Account-Id") + && let Ok(hv) = reqwest::header::HeaderValue::from_str(&acc) + { + headers.insert(name, hv); } } } @@ -322,22 +283,23 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a codex_login::AuthMode::ChatGPT, "codex_cloud_tasks_tui".to_string(), ); - if let Some(auth) = am.auth() { - if let Ok(token) = auth.get_token().await && !token.is_empty() { - if let Ok(hv) = - reqwest::header::HeaderValue::from_str(&format!("Bearer {token}")) - { - headers.insert(reqwest::header::AUTHORIZATION, hv); - } - if let Some(account_id) = auth - .get_account_id() - .or_else(|| extract_chatgpt_account_id(&token)) - && let Ok(name) = - reqwest::header::HeaderName::from_bytes(b"ChatGPT-Account-Id") - && let Ok(hv) = reqwest::header::HeaderValue::from_str(&account_id) - { - headers.insert(name, hv); - } + if let Some(auth) = am.auth() + && let Ok(token) = auth.get_token().await + && !token.is_empty() + { + if let Ok(hv) = + reqwest::header::HeaderValue::from_str(&format!("Bearer {token}")) + { + headers.insert(reqwest::header::AUTHORIZATION, hv); + } + if let Some(account_id) = auth + .get_account_id() + .or_else(|| extract_chatgpt_account_id(&token)) + && let Ok(name) = + reqwest::header::HeaderName::from_bytes(b"ChatGPT-Account-Id") + && let Ok(hv) = reqwest::header::HeaderValue::from_str(&account_id) + { + headers.insert(name, hv); } } } @@ -388,7 +350,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a let _ = frame_tx.send(Instant::now()); // Render helper to centralize immediate redraws after handling events. - let mut render_if_needed = |terminal: &mut Terminal>, + let render_if_needed = |terminal: &mut Terminal>, app: &mut app::App, needs_redraw: &mut bool| -> anyhow::Result<()> { @@ -480,22 +442,12 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a } } } - app::AppEvent::TaskSummaryUpdated { generation, id, summary, no_diff_yet, environment_id: _ } => { - // Ignore stale generations - if generation != app.list_generation { continue; } - let id_str = id.0.clone(); - if let Some(t) = app.tasks.iter_mut().find(|t| t.id.0 == id_str) { - t.summary = summary.clone(); - } - app.summary_cache.insert(id_str.clone(), (summary, std::time::Instant::now())); - if no_diff_yet { app.no_diff_yet.insert(id_str); } else { app.no_diff_yet.remove(&id.0); } - needs_redraw = true; - let _ = frame_tx.send(Instant::now()); - } + // (removed TaskSummaryUpdated; unused in this prototype) app::AppEvent::ApplyPreflightFinished { id, title, message, level, skipped, conflicts } => { // Only update if modal is still open and ids match - if let Some(m) = app.apply_modal.as_mut() { - if m.task_id == id { + if let Some(m) = app.apply_modal.as_mut() + && m.task_id == id + { m.title = title; m.result_message = Some(message); m.result_level = Some(level); @@ -504,7 +456,6 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a app.apply_preflight_inflight = false; needs_redraw = true; let _ = frame_tx.send(Instant::now()); - } } } app::AppEvent::EnvironmentsLoaded(result) => { @@ -543,7 +494,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a app.refresh_inflight = true; app.list_generation = app.list_generation.saturating_add(1); app.in_flight.clear(); - app.no_diff_yet.clear(); + // reset spinner state needs_redraw = true; let backend2 = backend.clone(); let tx2 = tx.clone(); @@ -571,15 +522,16 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a codex_login::AuthMode::ChatGPT, "codex_cloud_tasks_tui".to_string(), ); - if let Some(auth) = am.auth() { - if let Ok(tok) = auth.get_token().await && !tok.is_empty() { - let v = format!("Bearer {tok}"); - if let Ok(hv) = reqwest::header::HeaderValue::from_str(&v) { headers.insert(reqwest::header::AUTHORIZATION, hv); } - if let Some(acc) = auth.get_account_id().or_else(|| extract_chatgpt_account_id(&tok)) - && let Ok(name) = reqwest::header::HeaderName::from_bytes(b"ChatGPT-Account-Id") - && let Ok(hv) = reqwest::header::HeaderValue::from_str(&acc) { - headers.insert(name, hv); - } + if let Some(auth) = am.auth() + && let Ok(tok) = auth.get_token().await + && !tok.is_empty() + { + let v = format!("Bearer {tok}"); + if let Ok(hv) = reqwest::header::HeaderValue::from_str(&v) { headers.insert(reqwest::header::AUTHORIZATION, hv); } + if let Some(acc) = auth.get_account_id().or_else(|| extract_chatgpt_account_id(&tok)) + && let Ok(name) = reqwest::header::HeaderName::from_bytes(b"ChatGPT-Account-Id") + && let Ok(hv) = reqwest::header::HeaderValue::from_str(&acc) { + headers.insert(name, hv); } } } @@ -593,7 +545,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a } app::AppEvent::DetailsDiffLoaded { id, title, diff } => { // Only update if the overlay still corresponds to this id. - if let Some(ov) = &app.diff_overlay { if ov.task_id != id { continue; } } + if let Some(ov) = &app.diff_overlay && ov.task_id != id { continue; } let mut sd = crate::scrollable_diff::ScrollableDiff::new(); sd.set_content(diff.lines().map(|s| s.to_string()).collect()); app.diff_overlay = Some(app::DiffOverlay{ title, task_id: id, sd, can_apply: true }); @@ -602,7 +554,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a needs_redraw = true; } app::AppEvent::DetailsMessagesLoaded { id, title, messages } => { - if let Some(ov) = &app.diff_overlay { if ov.task_id != id { continue; } } + if let Some(ov) = &app.diff_overlay && ov.task_id != id { continue; } let mut lines = Vec::new(); for m in messages { lines.extend(m.lines().map(|s| s.to_string())); lines.push(String::new()); } if lines.is_empty() { lines.push("".to_string()); } @@ -614,7 +566,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a needs_redraw = true; } app::AppEvent::DetailsFailed { id, title, error } => { - if let Some(ov) = &app.diff_overlay { if ov.task_id != id { continue; } } + if let Some(ov) = &app.diff_overlay && ov.task_id != id { continue; } append_error_log(format!("details failed for {}: {error}", id.0)); let pretty = pretty_lines_from_error(&error); let mut sd = crate::scrollable_diff::ScrollableDiff::new(); @@ -777,7 +729,6 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a KeyCode::Char('y') => { if let Some(m) = app.apply_modal.take() { app.status = format!("Applying '{}'...", m.title); - needs_redraw = true; match codex_cloud_tasks_api::CloudBackend::apply_task(&*backend, m.task_id.clone()).await { Ok(outcome) => { app.status = outcome.message.clone(); @@ -791,7 +742,6 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a app.status = format!("Apply failed: {e}"); } } - needs_redraw = true; } } KeyCode::Char('p') => { @@ -990,7 +940,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a // Resolve selection over filtered set if let Some(state) = app.env_modal.take() { let q = state.query.to_lowercase(); - let mut filtered: Vec<&app::EnvironmentRow> = app.environments.iter().filter(|r| { + let filtered: Vec<&app::EnvironmentRow> = app.environments.iter().filter(|r| { if q.is_empty() { return true; } let mut hay = String::new(); if let Some(l) = &r.label { hay.push_str(&l.to_lowercase()); hay.push(' '); } @@ -1021,7 +971,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a app.refresh_inflight = true; app.list_generation = app.list_generation.saturating_add(1); app.in_flight.clear(); - app.no_diff_yet.clear(); + // reset spinner state needs_redraw = true; let backend2 = backend.clone(); let tx2 = tx.clone(); @@ -1059,7 +1009,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option) -> a app.refresh_inflight = true; app.list_generation = app.list_generation.saturating_add(1); app.in_flight.clear(); - app.no_diff_yet.clear(); + // reset spinner state needs_redraw = true; // Spawn background refresh let backend2 = backend.clone(); @@ -1251,8 +1201,9 @@ fn pretty_lines_from_error(raw: &str) -> Vec { } // Try to parse the embedded JSON body: find the first '{' after " body=" and decode. - if let Some(body_idx) = raw.find(" body=") { - if let Some(json_start_rel) = raw[body_idx..].find('{') { + if let Some(body_idx) = raw.find(" body=") + && let Some(json_start_rel) = raw[body_idx..].find('{') + { let json_start = body_idx + json_start_rel; let json_str = raw[json_start..].trim(); if let Ok(v) = serde_json::from_str::(json_str) { @@ -1288,15 +1239,13 @@ fn pretty_lines_from_error(raw: &str) -> Vec { .get("latest_event") .and_then(|e| e.get("text")) .and_then(|s| s.as_str()) + && !text.trim().is_empty() { - if !text.trim().is_empty() { - lines.push(format!("Latest event: {}", text.trim())); - } + lines.push(format!("Latest event: {}", text.trim())); } } } } - } if lines.len() == 1 { // Parsing yielded nothing; include a trimmed, short raw message tail for context. diff --git a/codex-rs/cloud-tasks/src/new_task.rs b/codex-rs/cloud-tasks/src/new_task.rs index 2ef30ad629..762e76dac0 100644 --- a/codex-rs/cloud-tasks/src/new_task.rs +++ b/codex-rs/cloud-tasks/src/new_task.rs @@ -1,4 +1,3 @@ -use codex_tui::ComposerAction; use codex_tui::ComposerInput; #[derive(Default)] @@ -24,7 +23,5 @@ impl NewTaskPage { } } - pub fn can_submit(&self) -> bool { - self.env_id.is_some() && !self.submitting - } + // Additional helpers can be added as usage evolves. } diff --git a/codex-rs/cloud-tasks/src/scrollable_diff.rs b/codex-rs/cloud-tasks/src/scrollable_diff.rs index 7666588cd2..813675050b 100644 --- a/codex-rs/cloud-tasks/src/scrollable_diff.rs +++ b/codex-rs/cloud-tasks/src/scrollable_diff.rs @@ -144,15 +144,11 @@ impl ScrollableDiff { out.push(prefix.trim_end().to_string()); out_idx.push(raw_idx); line = rest.trim_start().to_string(); - line_cols = UnicodeWidthStr::width(line.as_str()); last_soft_idx = None; // retry add current ch now that line may be shorter - } else { - if !line.is_empty() { - out.push(std::mem::take(&mut line)); - out_idx.push(raw_idx); - line_cols = 0; - } + } else if !line.is_empty() { + out.push(std::mem::take(&mut line)); + out_idx.push(raw_idx); } } if ch.is_whitespace() diff --git a/codex-rs/cloud-tasks/src/ui.rs b/codex-rs/cloud-tasks/src/ui.rs index 5ca637c397..665e948980 100644 --- a/codex-rs/cloud-tasks/src/ui.rs +++ b/codex-rs/cloud-tasks/src/ui.rs @@ -150,10 +150,10 @@ pub fn draw_new_task_page(frame: &mut Frame, area: Rect, app: &mut App) { } // Place cursor where composer wants it - if let Some(page) = app.new_task.as_ref() { - if let Some((x, y)) = page.composer.cursor_pos(composer_area) { - frame.set_cursor_position((x, y)); - } + if let Some(page) = app.new_task.as_ref() + && let Some((x, y)) = page.composer.cursor_pos(composer_area) + { + frame.set_cursor_position((x, y)); } } @@ -627,22 +627,7 @@ fn draw_centered_spinner( draw_inline_spinner(frame, cols[1], state, label); } -fn style_diff_fragment(src_line: &str, fragment: &str) -> Line<'static> { - if src_line.starts_with("@@") { - return Line::from(fragment.to_string().magenta().bold()); - } - if src_line.starts_with("diff --git ") || src_line.starts_with("index ") { - return Line::from(fragment.to_string().dim()); - } - if src_line.starts_with("+++") || src_line.starts_with("---") { - return Line::from(fragment.to_string().dim()); - } - match src_line.as_bytes().first().copied() { - Some(b'+') => Line::from(fragment.to_string().green()), - Some(b'-') => Line::from(fragment.to_string().red()), - _ => Line::from(fragment.to_string()), - } -} +// Styling helpers for diff rendering live inline where used. pub fn draw_env_modal(frame: &mut Frame, area: Rect, app: &mut App) { use ratatui::widgets::Wrap; diff --git a/codex-rs/cloud-tasks/tests/env_filter.rs b/codex-rs/cloud-tasks/tests/env_filter.rs index 26c6d9d8e3..7ff5238f82 100644 --- a/codex-rs/cloud-tasks/tests/env_filter.rs +++ b/codex-rs/cloud-tasks/tests/env_filter.rs @@ -5,7 +5,7 @@ use codex_cloud_tasks_client::MockClient; #[tokio::test] async fn mock_backend_varies_by_env() { - let client = MockClient::default(); + let client = MockClient; let root = CloudBackend::list_tasks(&client, None).await.unwrap(); assert!(root.iter().any(|t| t.title.contains("Update README"))); diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 8b0a15ed3e..1bf7ef0c00 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -735,10 +735,7 @@ async fn includes_user_instructions_message_in_request() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn azure_overrides_assign_properties_used_for_responses_url() { - let (existing_env_var_with_random_value, existing_env_var_value) = std::env::vars() - .find(|(_, value)| !value.trim().is_empty()) - .expect("system environment variable should exist"); - let auth_header_value = format!("Bearer {existing_env_var_value}"); + let existing_env_var_with_random_value = if cfg!(windows) { "USERNAME" } else { "USER" }; // Mock server let server = MockServer::start().await; @@ -753,7 +750,14 @@ async fn azure_overrides_assign_properties_used_for_responses_url() { .and(path("/openai/responses")) .and(query_param("api-version", "2025-04-01-preview")) .and(header_regex("Custom-Header", "Value")) - .and(header_regex("Authorization", auth_header_value.as_str())) + .and(header_regex( + "Authorization", + format!( + "Bearer {}", + std::env::var(existing_env_var_with_random_value).unwrap() + ) + .as_str(), + )) .respond_with(first) .expect(1) .mount(&server) @@ -763,7 +767,7 @@ async fn azure_overrides_assign_properties_used_for_responses_url() { name: "custom".to_string(), base_url: Some(format!("{}/openai", server.uri())), // Reuse the existing environment variable to avoid using unsafe code - env_key: Some(existing_env_var_with_random_value.clone()), + env_key: Some(existing_env_var_with_random_value.to_string()), query_params: Some(std::collections::HashMap::from([( "api-version".to_string(), "2025-04-01-preview".to_string(), @@ -807,10 +811,7 @@ async fn azure_overrides_assign_properties_used_for_responses_url() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn env_var_overrides_loaded_auth() { - let (existing_env_var_with_random_value, existing_env_var_value) = std::env::vars() - .find(|(_, value)| !value.trim().is_empty()) - .expect("system environment variable should exist"); - let auth_header_value = format!("Bearer {existing_env_var_value}"); + let existing_env_var_with_random_value = if cfg!(windows) { "USERNAME" } else { "USER" }; // Mock server let server = MockServer::start().await; @@ -825,7 +826,14 @@ async fn env_var_overrides_loaded_auth() { .and(path("/openai/responses")) .and(query_param("api-version", "2025-04-01-preview")) .and(header_regex("Custom-Header", "Value")) - .and(header_regex("Authorization", auth_header_value.as_str())) + .and(header_regex( + "Authorization", + format!( + "Bearer {}", + std::env::var(existing_env_var_with_random_value).unwrap() + ) + .as_str(), + )) .respond_with(first) .expect(1) .mount(&server) @@ -835,7 +843,7 @@ async fn env_var_overrides_loaded_auth() { name: "custom".to_string(), base_url: Some(format!("{}/openai", server.uri())), // Reuse the existing environment variable to avoid using unsafe code - env_key: Some(existing_env_var_with_random_value.clone()), + env_key: Some(existing_env_var_with_random_value.to_string()), query_params: Some(std::collections::HashMap::from([( "api-version".to_string(), "2025-04-01-preview".to_string(), diff --git a/codex-rs/mcp-server/src/codex_message_processor.rs b/codex-rs/mcp-server/src/codex_message_processor.rs index 8fcac6f3da..2309e01dcd 100644 --- a/codex-rs/mcp-server/src/codex_message_processor.rs +++ b/codex-rs/mcp-server/src/codex_message_processor.rs @@ -180,7 +180,11 @@ impl CodexMessageProcessor { let opts = LoginServerOptions { open_browser: false, - ..LoginServerOptions::new(config.codex_home.clone(), CLIENT_ID.to_string()) + ..LoginServerOptions::new( + config.codex_home.clone(), + CLIENT_ID.to_string(), + config.responses_originator_header.clone(), + ) }; enum LoginChatGptReply { diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index f9fa70ae00..ff72509d52 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -1261,7 +1261,7 @@ impl WidgetRef for ChatComposer { let mut out: Vec = Vec::new(); for (i, (key, label)) in items.iter().enumerate() { out.push(Span::from(" ")); - out.push(key.as_str().set_style(key_hint_style)); + out.push(key_hint::plain(key)); out.push(Span::from(format!(" {label}"))); if i + 1 != items.len() { out.push(Span::from(" ")); diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 2139377873..067bac4eb6 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -324,13 +324,11 @@ async fn run_ratatui_app( if should_show_onboarding { let directory_trust_decision = run_onboarding_app( OnboardingScreenArgs { - codex_home: config.codex_home.clone(), - cwd: config.cwd.clone(), show_login_screen: should_show_login_screen(login_status, &config), show_trust_screen: should_show_trust_screen, login_status, - preferred_auth_method: config.preferred_auth_method, auth_manager: auth_manager.clone(), + config: config.clone(), }, &mut tui, ) diff --git a/codex-rs/tui/src/onboarding/auth.rs b/codex-rs/tui/src/onboarding/auth.rs index c7217ac1af..90b2c994f1 100644 --- a/codex-rs/tui/src/onboarding/auth.rs +++ b/codex-rs/tui/src/onboarding/auth.rs @@ -2,6 +2,7 @@ use codex_core::AuthManager; use codex_core::auth::CLIENT_ID; +use codex_core::config::Config; use codex_login::ServerOptions; use codex_login::ShutdownHandle; use codex_login::run_login_server; @@ -113,6 +114,7 @@ pub(crate) struct AuthModeWidget { pub login_status: LoginStatus, pub preferred_auth_method: AuthMode, pub auth_manager: Arc, + pub config: Config, } impl AuthModeWidget { @@ -314,7 +316,11 @@ impl AuthModeWidget { } self.error = None; - let opts = ServerOptions::new(self.codex_home.clone(), CLIENT_ID.to_string()); + let opts = ServerOptions::new( + self.codex_home.clone(), + CLIENT_ID.to_string(), + self.config.responses_originator_header.clone(), + ); match run_login_server(opts) { Ok(child) => { let sign_in_state = self.sign_in_state.clone(); diff --git a/codex-rs/tui/src/onboarding/onboarding_screen.rs b/codex-rs/tui/src/onboarding/onboarding_screen.rs index 5afd393c02..beb96bce3f 100644 --- a/codex-rs/tui/src/onboarding/onboarding_screen.rs +++ b/codex-rs/tui/src/onboarding/onboarding_screen.rs @@ -1,4 +1,5 @@ use codex_core::AuthManager; +use codex_core::config::Config; use codex_core::git_info::get_git_repo_root; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; @@ -21,7 +22,6 @@ use crate::tui::FrameRequester; use crate::tui::Tui; use crate::tui::TuiEvent; use color_eyre::eyre::Result; -use std::path::PathBuf; use std::sync::Arc; use std::sync::RwLock; @@ -53,26 +53,25 @@ pub(crate) struct OnboardingScreen { } pub(crate) struct OnboardingScreenArgs { - pub codex_home: PathBuf, - pub cwd: PathBuf, pub show_trust_screen: bool, pub show_login_screen: bool, pub login_status: LoginStatus, - pub preferred_auth_method: AuthMode, pub auth_manager: Arc, + pub config: Config, } impl OnboardingScreen { pub(crate) fn new(tui: &mut Tui, args: OnboardingScreenArgs) -> Self { let OnboardingScreenArgs { - codex_home, - cwd, show_trust_screen, show_login_screen, login_status, - preferred_auth_method, auth_manager, + config, } = args; + let preferred_auth_method = config.preferred_auth_method; + let cwd = config.cwd.clone(); + let codex_home = config.codex_home.clone(); let mut steps: Vec = vec![Step::Welcome(WelcomeWidget { is_logged_in: !matches!(login_status, LoginStatus::NotAuthenticated), })]; @@ -84,8 +83,9 @@ impl OnboardingScreen { sign_in_state: Arc::new(RwLock::new(SignInState::PickMode)), codex_home: codex_home.clone(), login_status, - preferred_auth_method, auth_manager, + preferred_auth_method, + config, })) } let is_git_repo = get_git_repo_root(&cwd).is_some();