merge: upstream/main into fork (prefer ours)\n\n- TUI: adopt upstream custom_terminal and auth widget; keep themes/browser/agents intact\n- Fix stable Rust let-chains across core/tui/cli/mcp-server\n- Align toolchains: build-fast.sh uses rustup (1.89.0 via rust-toolchain.toml); release.yml -> 1.89.0\n- core: file locking via fs2, remove std TryLockError usage\n- mcp-server: keep our impl; avoid upstream-only EventMsg variant\n- TUI fixes: selection list module/export, render_rows arg, history token usage, formatting utils\n- cli/login: remove preferred_auth_method, fix env var let-chain\n\nBuild: ./build-fast.sh passes with Rust 1.89.0

This commit is contained in:
James Peter
2025-08-20 09:55:17 +10:00
parent 035569a656
commit 14148bbdab
13 changed files with 46 additions and 50 deletions
+4 -4
View File
@@ -60,19 +60,19 @@ pub async fn run_login_with_api_key(
pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! {
let config = load_config_or_exit(cli_config_overrides);
match CodexAuth::from_codex_home(&config.codex_home, config.preferred_auth_method) {
match CodexAuth::from_codex_home(&config.codex_home, AuthMode::ApiKey) {
Ok(Some(auth)) => match auth.mode {
AuthMode::ApiKey => match auth.get_token().await {
Ok(api_key) => {
eprintln!("Logged in using an API key - {}", safe_format_key(&api_key));
if let Ok(env_api_key) = env::var(OPENAI_API_KEY_ENV_VAR)
&& env_api_key == api_key
{
if let Ok(env_api_key) = env::var(OPENAI_API_KEY_ENV_VAR) {
if env_api_key == api_key {
eprintln!(
" API loaded from OPENAI_API_KEY environment variable or .env file"
);
}
}
std::process::exit(0);
}
Err(e) => {
+2 -2
View File
@@ -130,7 +130,7 @@ async fn acquire_exclusive_lock_with_retry(file: &File) -> Result<()> {
use tokio::time::sleep;
for _ in 0..MAX_RETRIES {
match file.try_lock_exclusive() {
match fs2::FileExt::try_lock_exclusive(file) {
Ok(()) => return Ok(()),
Err(e) => {
if e.kind() == std::io::ErrorKind::WouldBlock {
@@ -263,7 +263,7 @@ pub(crate) fn lookup(log_id: u64, offset: usize, config: &Config) -> Option<Hist
#[cfg(unix)]
fn acquire_shared_lock_with_retry(file: &File) -> Result<()> {
for _ in 0..MAX_RETRIES {
match file.try_lock_shared() {
match fs2::FileExt::try_lock_shared(file) {
Ok(()) => return Ok(()),
Err(e) => {
if e.kind() == std::io::ErrorKind::WouldBlock {
+1 -1
View File
@@ -263,7 +263,7 @@ async fn run_codex_tool_session_inner(
| EventMsg::AgentReasoningSectionBreak(_)
| EventMsg::McpToolCallBegin(_)
| EventMsg::McpToolCallEnd(_)
| EventMsg::McpListToolsResponse(_)
// | EventMsg::McpListToolsResponse(_)
| EventMsg::ExecCommandBegin(_)
| EventMsg::ExecCommandOutputDelta(_)
| EventMsg::ExecCommandEnd(_)
@@ -33,11 +33,11 @@ impl ApprovalModalView<'_> {
/// Advance to next request if the current one is finished.
fn maybe_advance(&mut self) {
if self.current.is_complete()
&& let Some(req) = self.queue.pop()
{
if self.current.is_complete() {
if let Some(req) = self.queue.pop() {
self.current = UserApprovalWidget::new(req, self.app_event_tx.clone());
}
}
}
}
@@ -226,6 +226,7 @@ impl BottomPaneView<'_> for ListSelectionView {
match_indices: None,
is_current: it.is_current,
description: it.description.clone(),
name_color: None,
}
})
.collect();
+1
View File
@@ -24,6 +24,7 @@ mod popup_consts;
mod reasoning_selection_view;
mod scroll_state;
mod selection_popup_common;
pub mod list_selection_view;
mod textarea;
mod theme_selection_view;
mod verbosity_selection_view;
+4 -4
View File
@@ -33,10 +33,10 @@ where
return None;
}
if let Some(home_dir) = std::env::var_os("HOME").map(PathBuf::from)
&& let Ok(rel) = path.strip_prefix(&home_dir)
{
return Some(rel.to_path_buf());
if let Some(home_dir) = std::env::var_os("HOME").map(PathBuf::from) {
if let Ok(rel) = path.strip_prefix(&home_dir) {
return Some(rel.to_path_buf());
}
}
None
+6 -6
View File
@@ -94,13 +94,13 @@ impl FileSearchManager {
// If there is an in-flight search that is definitely obsolete,
// cancel it now.
if let Some(active_search) = &st.active_search
&& !query.starts_with(&active_search.query)
{
if let Some(active_search) = &st.active_search {
if !query.starts_with(&active_search.query) {
active_search
.cancellation_token
.store(true, Ordering::Relaxed);
st.active_search = None;
}
}
// Schedule a search to run after debounce.
@@ -187,11 +187,11 @@ impl FileSearchManager {
{
#[expect(clippy::unwrap_used)]
let mut st = search_state.lock().unwrap();
if let Some(active_search) = &st.active_search
&& Arc::ptr_eq(&active_search.cancellation_token, &cancellation_token)
{
if let Some(active_search) = &st.active_search {
if Arc::ptr_eq(&active_search.cancellation_token, &cancellation_token) {
st.active_search = None;
}
}
}
});
}
+3 -9
View File
@@ -2013,22 +2013,16 @@ pub(crate) fn new_status_output(config: &Config, usage: &TokenUsage) -> PlainHis
// 📊 Token Usage
lines.push(Line::from(vec!["📊 ".into(), "Token Usage".bold()]));
if let Some(session_id) = session_id {
lines.push(Line::from(vec![
" • Session ID: ".into(),
session_id.to_string().into(),
]));
}
// Input: <input> [+ <cached> cached]
let mut input_line_spans: Vec<Span<'static>> = vec![
" • Input: ".into(),
usage.non_cached_input().to_string().into(),
];
if let Some(cached) = usage.cached_input_tokens
&& cached > 0
{
if let Some(cached) = usage.cached_input_tokens {
if cached > 0 {
input_line_spans.push(format!(" (+ {cached} cached)").into());
}
}
lines.push(Line::from(input_line_spans));
// Output: <output>
lines.push(Line::from(vec![
+1
View File
@@ -318,6 +318,7 @@ fn restore() {
}
/// Minimal login status indicator for onboarding flow.
#[derive(Debug, Clone, Copy)]
pub enum LoginStatus {
NotAuthenticated,
AuthMode(AuthMode),
+13 -14
View File
@@ -123,9 +123,8 @@ impl AuthModeWidget {
// If the user is already authenticated but the method differs from their
// preferred auth method, show a brief explanation.
if let LoginStatus::AuthMode(current) = self.login_status
&& current != self.preferred_auth_method
{
if let LoginStatus::AuthMode(current) = self.login_status {
if current != self.preferred_auth_method {
let to_label = |mode: AuthMode| match mode {
AuthMode::ApiKey => "API key",
AuthMode::ChatGPT => "ChatGPT",
@@ -138,6 +137,7 @@ impl AuthModeWidget {
lines.push(Line::from(msg).style(Style::default()));
lines.push(Line::from(""));
}
}
let create_mode_item = |idx: usize,
selected_mode: AuthMode,
@@ -221,16 +221,15 @@ impl AuthModeWidget {
)));
spans.extend(shimmer_spans("Finish signing in via your browser"));
let mut lines = vec![Line::from(spans), Line::from("")];
if let SignInState::ChatGptContinueInBrowser(state) = &self.sign_in_state
&& !state.auth_url.is_empty()
{
lines.push(Line::from(" If the link doesn't open automatically, open the following link to authenticate:"));
lines.push(Line::from(vec![
Span::raw(" "),
state.auth_url.as_str().cyan().underlined(),
]));
lines.push(Line::from(""));
if let SignInState::ChatGptContinueInBrowser(state) = &self.sign_in_state {
if !state.auth_url.is_empty() {
lines.push(Line::from(" If the link doesn't open automatically, open the following link to authenticate:"));
lines.push(Line::from(vec![
Span::raw(" "),
state.auth_url.as_str().cyan().underlined(),
]));
lines.push(Line::from(""));
}
}
lines.push(
@@ -412,4 +411,4 @@ impl WidgetRef for AuthModeWidget {
}
}
}
}
}
@@ -81,7 +81,7 @@ impl OnboardingScreen {
sign_in_state: SignInState::PickMode,
codex_home: codex_home.clone(),
login_status,
preferred_auth_method: chat_widget_args.config.preferred_auth_method,
preferred_auth_method: codex_login::AuthMode::ApiKey,
}))
}
let is_git_repo = is_inside_git_repo(&cwd);
+6 -6
View File
@@ -53,12 +53,12 @@ pub(crate) fn format_json_compact(text: &str) -> Option<String> {
}
' ' | '\t' if !in_string => {
// Add a space after : and , but only when not in a string
if let Some(&next_ch) = chars.peek()
&& let Some(last_ch) = result.chars().last()
&& (last_ch == ':' || last_ch == ',')
&& !matches!(next_ch, '}' | ']')
{
result.push(' ');
if let Some(&next_ch) = chars.peek() {
if let Some(last_ch) = result.chars().last() {
if (last_ch == ':' || last_ch == ',') && !matches!(next_ch, '}' | ']') {
result.push(' ');
}
}
}
}
_ => {