mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
Extend server version notices to prerelease and local clients (#46673)
## Why Server version notices previously required both versions to be stable releases, leaving prerelease and local clients without notices about differing background services. ## What changed - Use semantic version parsing to distinguish older servers from version mismatches. - Compare stable clients against older releases, including prereleases. Compare prerelease clients by precedence within the same release line and report other release lines as different. - Report differing versions for local clients (`0.0.0` or versions with build metadata). Ignore malformed versions. - Show the corresponding `older than` or `different from` notice and `<` or `≠` overview indicator, while preserving `/daemon` guidance and the `show_server_version_notice` setting. ## Testing Add version-policy tests and notice snapshots covering prerelease ordering, local builds, mismatches, and malformed versions. Add daemon tests for notice suppression and manual update guidance without scheduling an update. GitOrigin-RevId: 29df4713a7dbcbddcf30d56995a8496e7e8d0c6a
This commit is contained in:
Generated
+1
@@ -4855,6 +4855,7 @@ dependencies = [
|
||||
"ratatui-macros",
|
||||
"regex-lite",
|
||||
"rmcp",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serial_test",
|
||||
|
||||
@@ -763,7 +763,7 @@ pub struct Tui {
|
||||
#[serde(default = "default_true")]
|
||||
pub show_tooltips: bool,
|
||||
|
||||
/// Show an informational notice when the connected app server is an older stable release.
|
||||
/// Show informational notices about connected app server version differences.
|
||||
/// Defaults to `true`; this does not control compatibility errors or version status.
|
||||
#[serde(default = "default_true")]
|
||||
pub show_server_version_notice: bool,
|
||||
|
||||
@@ -4479,7 +4479,7 @@
|
||||
},
|
||||
"show_server_version_notice": {
|
||||
"default": true,
|
||||
"description": "Show an informational notice when the connected app server is an older stable release. Defaults to `true`; this does not control compatibility errors or version status.",
|
||||
"description": "Show informational notices about connected app server version differences. Defaults to `true`; this does not control compatibility errors or version status.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"show_tooltips": {
|
||||
|
||||
@@ -99,6 +99,7 @@ ratatui = { workspace = true, features = [
|
||||
ratatui-macros = { workspace = true }
|
||||
regex-lite = { workspace = true }
|
||||
rmcp = { workspace = true, features = ["server", "transport-streamable-http-server"] }
|
||||
semver = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true, features = ["preserve_order"] }
|
||||
sha2 = { workspace = true }
|
||||
|
||||
@@ -76,7 +76,7 @@ async fn server_version_overview_notice_updates_and_clears() {
|
||||
insta::assert_snapshot!(rendered.lines().take(2).collect::<Vec<_>>().join("\n"), @" Service v0.151.0 < Codex CLI v0.153.0
|
||||
0 need input 0 working 0 ready");
|
||||
|
||||
app.update_server_version_overview_notice("0.153.0", /*older_server*/ None);
|
||||
app.update_server_version_overview_notice("0.153.0", /*server_version*/ None);
|
||||
let view = app.agents_overview_view(Vec::new(), /*selected_thread_id*/ None);
|
||||
app.chat_widget.show_bottom_pane_view(Box::new(view));
|
||||
let rendered = render_bottom_popup(&app.chat_widget, /*width*/ 80);
|
||||
|
||||
@@ -6,6 +6,55 @@ use crate::status::remote_connection::RemoteConnectionStatus;
|
||||
use crossterm::event::KeyCode;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[tokio::test]
|
||||
async fn daemon_version_notice_preserves_manual_update_guidance() {
|
||||
let mut app = make_test_app().await;
|
||||
app.app_server_target = AppServerTarget::LocalDaemon {
|
||||
allow_embedded_fallback: true,
|
||||
endpoint: crate::RemoteAppServerEndpoint::UnixSocket {
|
||||
socket_path: AbsolutePathBuf::relative_to_current_dir("codex.sock").unwrap(),
|
||||
},
|
||||
};
|
||||
let view = app.agents_overview_view(Vec::new(), /*selected_thread_id*/ None);
|
||||
app.chat_widget.show_bottom_pane_view(Box::new(view));
|
||||
let mut notices = Vec::new();
|
||||
for (client, server, comparison) in [
|
||||
("0.155.0-alpha.23", "0.155.0-alpha.22", "older than"),
|
||||
("0.155.0-alpha.23", "0.156.0", "different from"),
|
||||
("0.0.0", "0.156.0", "different from"),
|
||||
] {
|
||||
app.local_settings.tui.show_server_version_notice = true;
|
||||
assert_eq!(
|
||||
app.initialize_server_version_notice(client, Some(server)),
|
||||
Some(format!(
|
||||
"A background Codex service is running v{server}, {comparison} your Codex CLI v{client}."
|
||||
))
|
||||
);
|
||||
let overview = render_bottom_popup(&app.chat_widget, /*width*/ 100);
|
||||
notices.push(overview.lines().next().unwrap().trim().to_string());
|
||||
assert_eq!(app.pending_update_action, None);
|
||||
|
||||
app.local_settings.tui.show_server_version_notice = false;
|
||||
assert_eq!(
|
||||
app.initialize_server_version_notice(client, Some(server)),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
app.agents_overview
|
||||
.view_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.server_version_notice,
|
||||
None
|
||||
);
|
||||
}
|
||||
insta::assert_snapshot!(notices.join("\n"), @"
|
||||
Service v0.155.0-alpha.22 < Codex CLI v0.155.0-alpha.23 · /daemon
|
||||
Service v0.156.0 ≠ Codex CLI v0.155.0-alpha.23 · /daemon
|
||||
Service v0.156.0 ≠ Codex CLI v0.0.0 · /daemon
|
||||
");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn daemon_menu_is_read_only_and_confirmation_can_cancel_or_handoff() {
|
||||
let mut app = make_test_app().await;
|
||||
|
||||
@@ -189,7 +189,7 @@ impl App {
|
||||
self.reconnect.seen_version_notice = None;
|
||||
self.update_server_version_overview_notice(
|
||||
CODEX_CLI_VERSION,
|
||||
/*older_server*/ None,
|
||||
/*server_version*/ None,
|
||||
);
|
||||
}
|
||||
self.cancel_pending_key_chord();
|
||||
@@ -492,7 +492,10 @@ impl App {
|
||||
|| self.reconnect.seen_version_notice != connected_notice_key
|
||||
{
|
||||
self.reconnect.seen_version_notice = None;
|
||||
self.update_server_version_overview_notice(client_version, /*older_server*/ None);
|
||||
self.update_server_version_overview_notice(
|
||||
client_version,
|
||||
/*server_version*/ None,
|
||||
);
|
||||
}
|
||||
if let Some((notice, key)) = crate::status::remote_connection::pending_server_version_notice(
|
||||
&self.local_settings.tui,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Keeps the older server notice and local maintenance guidance in the overview.
|
||||
//! Keeps the server version notice and local maintenance guidance in the overview.
|
||||
|
||||
use super::*;
|
||||
use crate::update_versions::ServerVersionNoticeKind;
|
||||
|
||||
impl App {
|
||||
pub(super) fn refresh_server_version_overview_notice(&mut self, client_version: &str) {
|
||||
@@ -41,20 +42,27 @@ impl App {
|
||||
pub(super) fn update_server_version_overview_notice(
|
||||
&mut self,
|
||||
client_version: &str,
|
||||
older_server: Option<&str>,
|
||||
server_version: Option<&str>,
|
||||
) {
|
||||
self.agents_overview
|
||||
.view_state
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.server_version_notice = older_server.map(|server| {
|
||||
.server_version_notice = server_version.and_then(|server| {
|
||||
let guidance = if matches!(self.app_server_target, AppServerTarget::LocalDaemon { .. })
|
||||
{
|
||||
" · /daemon"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
format!("Service v{server} < Codex CLI v{client_version}{guidance}")
|
||||
let comparison =
|
||||
match crate::update_versions::server_version_notice_kind(client_version, server)? {
|
||||
ServerVersionNoticeKind::Older => "<",
|
||||
ServerVersionNoticeKind::Different => "≠",
|
||||
};
|
||||
Some(format!(
|
||||
"Service v{server} {comparison} Codex CLI v{client_version}{guidance}"
|
||||
))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -826,7 +826,7 @@ See the Codex keymap documentation for supported actions and examples."
|
||||
if initial_server_version_notice.is_none() {
|
||||
app.update_server_version_overview_notice(
|
||||
CODEX_CLI_VERSION,
|
||||
/*older_server*/ None,
|
||||
/*server_version*/ None,
|
||||
);
|
||||
}
|
||||
if start_in_agents_overview {
|
||||
|
||||
+3
-1
@@ -1,9 +1,11 @@
|
||||
---
|
||||
source: tui/src/app/tests/active_reconnect_tests.rs
|
||||
expression: "format!(\"{history}\\n{}\", render_bottom_popup(&app.chat_widget, 80))"
|
||||
expression: "format!(\"{history}\\n{}\",\nrender_bottom_popup(&app.chat_widget,\n80)).replace(&test_path_buf(\"/tmp/project\").display().to_string(),\n\"/tmp/project\")"
|
||||
---
|
||||
• This conversation is unavailable. Its cached transcript and draft remain here; input is paused. Open the agent picker or return to the parent to continue.
|
||||
• Reconnected. No input was resent. Review uncertain submissions before retrying; recovered queues remain paused.
|
||||
⚠ A background Codex service is running v2.0.0, different from your Codex CLI
|
||||
v0.0.0.
|
||||
› unavailable draft
|
||||
|
||||
gpt-test default · /tmp/project
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
source: tui/src/app/tests/navigation_reconnect_tests.rs
|
||||
expression: "render_bottom_popup(&app.chat_widget, 100)"
|
||||
---
|
||||
Agent command center
|
||||
Service v2.0.0 ≠ Codex CLI v0.0.0 · /daemon
|
||||
0 need input 1 working 1 ready
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
/tmp/project 2 │ Task details
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
---
|
||||
source: tui/src/history_cell/tests.rs
|
||||
expression: "render_lines(&cell.display_lines(100)).join(\"\\n\")"
|
||||
---
|
||||
⚠ A background Codex service is running v0.156.0, different from your Codex CLI v0.155.0-alpha.12.
|
||||
Use /daemon to manage the local background server.
|
||||
Updating may interrupt active or queued work.
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
---
|
||||
source: tui/src/history_cell/tests.rs
|
||||
expression: "render_lines(&cell.display_lines(100)).join(\"\\n\")"
|
||||
---
|
||||
⚠ A background Codex service is running v0.156.0, different from your Codex CLI v0.0.0.
|
||||
Use /daemon to manage the local background server.
|
||||
Updating may interrupt active or queued work.
|
||||
@@ -78,17 +78,26 @@ fn local_daemon_version_notice_snapshot() {
|
||||
show_server_version_notice: true,
|
||||
..Default::default()
|
||||
};
|
||||
let (notice, _) = crate::status::remote_connection::pending_server_version_notice(
|
||||
&settings,
|
||||
&target,
|
||||
/*server_home*/ None,
|
||||
"0.153.0",
|
||||
Some("0.152.1"),
|
||||
/*last_shown*/ None,
|
||||
)
|
||||
.expect("older local service should have a notice");
|
||||
let cell = new_server_version_warning(notice);
|
||||
insta::assert_snapshot!(render_lines(&cell.display_lines(/*width*/ 100)).join("\n"));
|
||||
for (client, server, snapshot) in [
|
||||
("0.153.0", "0.152.1", "local_daemon_version_notice_snapshot"),
|
||||
("0.155.0-alpha.12", "0.156.0", "local_daemon_alpha_mismatch"),
|
||||
("0.0.0", "0.156.0", "local_daemon_source_mismatch"),
|
||||
] {
|
||||
let (notice, _) = crate::status::remote_connection::pending_server_version_notice(
|
||||
&settings,
|
||||
&target,
|
||||
/*server_home*/ None,
|
||||
client,
|
||||
Some(server),
|
||||
/*last_shown*/ None,
|
||||
)
|
||||
.expect("local service should have a notice");
|
||||
let cell = new_server_version_warning(notice);
|
||||
insta::assert_snapshot!(
|
||||
snapshot,
|
||||
render_lines(&cell.display_lines(/*width*/ 100)).join("\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_config() -> Config {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::AppServerTarget;
|
||||
use crate::RemoteAppServerEndpoint;
|
||||
use crate::update_versions::ServerVersionNoticeKind;
|
||||
use sha2::Digest;
|
||||
use sha2::Sha256;
|
||||
use url::Url;
|
||||
@@ -44,11 +45,13 @@ pub(crate) fn remote_connection_status_value(
|
||||
|
||||
pub(crate) fn server_version_notice(client: &str, server: Option<&str>) -> Option<String> {
|
||||
let server = server?;
|
||||
crate::update_versions::is_official_server_older(client, server).then(|| {
|
||||
format!(
|
||||
"A background Codex service is running v{server}, older than your Codex CLI v{client}."
|
||||
)
|
||||
})
|
||||
let comparison = match crate::update_versions::server_version_notice_kind(client, server)? {
|
||||
ServerVersionNoticeKind::Older => "older than",
|
||||
ServerVersionNoticeKind::Different => "different from",
|
||||
};
|
||||
Some(format!(
|
||||
"A background Codex service is running v{server}, {comparison} your Codex CLI v{client}."
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn server_version_notice_for_tui(
|
||||
@@ -205,14 +208,31 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_version_notice_only_for_older_official_server() {
|
||||
fn server_version_notice_uses_client_release_policy() {
|
||||
assert_eq!(
|
||||
server_version_notice("0.153.0", Some("0.152.1")),
|
||||
Some("A background Codex service is running v0.152.1, older than your Codex CLI v0.153.0.".to_string())
|
||||
);
|
||||
assert_eq!(server_version_notice("0.153.0", Some("0.153.0")), None);
|
||||
assert_eq!(server_version_notice("0.0.0", Some("0.152.1")), None);
|
||||
assert_eq!(
|
||||
server_version_notice("0.0.0", Some("0.152.1")),
|
||||
Some("A background Codex service is running v0.152.1, different from your Codex CLI v0.0.0.".to_string())
|
||||
);
|
||||
assert_eq!(server_version_notice("0.153.0", /*server*/ None), None);
|
||||
assert_eq!(
|
||||
server_version_notice("0.153.0-alpha.10", Some("0.153.0-alpha.9")),
|
||||
Some("A background Codex service is running v0.153.0-alpha.9, older than your Codex CLI v0.153.0-alpha.10.".to_string())
|
||||
);
|
||||
for server in ["0.153.0-alpha.10", "0.153.0-alpha.11", "0.153.0"] {
|
||||
assert_eq!(
|
||||
server_version_notice("0.153.0-alpha.10", Some(server)),
|
||||
None
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
server_version_notice("0.155.0-alpha.12", Some("0.156.0")),
|
||||
Some("A background Codex service is running v0.156.0, different from your Codex CLI v0.155.0-alpha.12.".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -234,13 +254,14 @@ mod tests {
|
||||
&settings,
|
||||
target,
|
||||
/*server_home*/ None,
|
||||
"0.153.0",
|
||||
Some("0.152.1"),
|
||||
"0.153.0-alpha.10.1",
|
||||
Some("0.153.0-alpha.9.2"),
|
||||
/*last_shown*/ None,
|
||||
)
|
||||
.map(|(notice, _)| notice)
|
||||
};
|
||||
let remote_notice = server_version_notice("0.153.0", Some("0.152.1")).unwrap();
|
||||
let remote_notice =
|
||||
server_version_notice("0.153.0-alpha.10.1", Some("0.153.0-alpha.9.2")).unwrap();
|
||||
assert_eq!(
|
||||
notice(&remote),
|
||||
Some(ServerVersionNotice {
|
||||
|
||||
@@ -19,30 +19,28 @@ pub(crate) fn is_source_build_version(version: &str) -> bool {
|
||||
parse_version(version) == Some((0, 0, 0))
|
||||
}
|
||||
|
||||
/// Whether an official stable TUI release is newer than the connected app server.
|
||||
pub(crate) fn is_official_server_older(client: &str, server: &str) -> bool {
|
||||
fn stable_version(version: &str) -> Option<(u64, u64, u64)> {
|
||||
fn component(value: Option<&str>) -> Option<u64> {
|
||||
let value = value?;
|
||||
if value.is_empty()
|
||||
|| !value.bytes().all(|byte| byte.is_ascii_digit())
|
||||
|| (value.len() > 1 && value.starts_with('0'))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
value.parse().ok()
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum ServerVersionNoticeKind {
|
||||
Older,
|
||||
Different,
|
||||
}
|
||||
|
||||
let mut parts = version.split('.');
|
||||
let version = (
|
||||
component(parts.next())?,
|
||||
component(parts.next())?,
|
||||
component(parts.next())?,
|
||||
);
|
||||
(parts.next().is_none() && version != (0, 0, 0)).then_some(version)
|
||||
/// Stable clients compare release precedence. Prerelease clients only order versions
|
||||
/// within the same release line; local builds and other release lines compare identity.
|
||||
pub(crate) fn server_version_notice_kind(
|
||||
client: &str,
|
||||
server: &str,
|
||||
) -> Option<ServerVersionNoticeKind> {
|
||||
let client = semver::Version::parse(client).ok()?;
|
||||
let server = semver::Version::parse(server).ok()?;
|
||||
let client_release = (client.major, client.minor, client.patch);
|
||||
let server_release = (server.major, server.minor, server.patch);
|
||||
let client_is_local = client_release == (0, 0, 0) || !client.build.is_empty();
|
||||
if client_is_local || (!client.pre.is_empty() && client_release != server_release) {
|
||||
return (client != server).then_some(ServerVersionNoticeKind::Different);
|
||||
}
|
||||
|
||||
matches!((stable_version(client), stable_version(server)), (Some(client), Some(server)) if client > server)
|
||||
(server.build.is_empty() && server_release != (0, 0, 0) && client > server)
|
||||
.then_some(ServerVersionNoticeKind::Older)
|
||||
}
|
||||
|
||||
#[cfg(any(not(debug_assertions), test))]
|
||||
@@ -99,24 +97,87 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn official_server_version_comparison() {
|
||||
assert!(is_official_server_older("0.152.1", "0.152.0"));
|
||||
assert!(is_official_server_older("0.153.0", "0.152.1"));
|
||||
assert!(!is_official_server_older("0.152.0", "0.152.0"));
|
||||
assert!(!is_official_server_older("0.152.0", "0.153.0"));
|
||||
fn stable_clients_only_warn_for_older_releases() {
|
||||
for (client, server, expected) in [
|
||||
("0.152.1", "0.152.0", Some(ServerVersionNoticeKind::Older)),
|
||||
("0.153.0", "0.152.1", Some(ServerVersionNoticeKind::Older)),
|
||||
(
|
||||
"0.153.0",
|
||||
"0.153.0-alpha.10.1",
|
||||
Some(ServerVersionNoticeKind::Older),
|
||||
),
|
||||
(
|
||||
"0.156.0",
|
||||
"0.155.0-alpha.12",
|
||||
Some(ServerVersionNoticeKind::Older),
|
||||
),
|
||||
("0.153.0", "0.153.0", None),
|
||||
("0.153.0", "0.154.0", None),
|
||||
("0.153.0", "0.154.0-alpha.1", None),
|
||||
("0.153.0", "0.0.0", None),
|
||||
("0.153.0", "0.0.0-alpha.1", None),
|
||||
("0.153.0", "0.152.0+dev", None),
|
||||
] {
|
||||
assert_eq!(server_version_notice_kind(client, server), expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prerelease_clients_compare_within_the_same_release_line() {
|
||||
for (newer, older) in [
|
||||
("0.155.0-alpha.23", "0.155.0-alpha.22"),
|
||||
("0.155.0-alpha.24", "0.155.0-alpha.23"),
|
||||
("0.153.0-alpha.10", "0.153.0-alpha.9"),
|
||||
("0.153.0-alpha.10.1", "0.153.0-alpha.9.2"),
|
||||
("0.153.0-alpha.10.10", "0.153.0-alpha.10.9"),
|
||||
("0.153.0-alpha.10.1", "0.153.0-alpha.10"),
|
||||
("0.155.0", "0.155.0-alpha.23"),
|
||||
] {
|
||||
assert_eq!(
|
||||
server_version_notice_kind(newer, older),
|
||||
Some(ServerVersionNoticeKind::Older)
|
||||
);
|
||||
assert_eq!(server_version_notice_kind(older, newer), None);
|
||||
assert_eq!(server_version_notice_kind(newer, newer), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prerelease_clients_on_other_release_lines_and_local_clients_warn_for_mismatches() {
|
||||
for (client, server) in [
|
||||
("0.155.0-alpha.23", "0.156.0"),
|
||||
("0.155.0-alpha.12", "0.154.0"),
|
||||
("0.0.0", "0.153.0"),
|
||||
("0.0.0", "0.153.0-alpha.10"),
|
||||
("0.153.0+dev", "0.153.0"),
|
||||
("0.153.0-alpha.10", "0.0.0"),
|
||||
] {
|
||||
assert_eq!(
|
||||
server_version_notice_kind(client, server),
|
||||
Some(ServerVersionNoticeKind::Different)
|
||||
);
|
||||
assert_eq!(server_version_notice_kind(client, client), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_or_malformed_versions_do_not_produce_notices() {
|
||||
for version in [
|
||||
"0.0.0",
|
||||
"0.0.0.0",
|
||||
"0.153.0-alpha.1",
|
||||
"unknown",
|
||||
"dev",
|
||||
"0.0.0.0",
|
||||
"0.153",
|
||||
"0.153.0.1",
|
||||
" 0.153.0",
|
||||
"+0.153.0",
|
||||
"0.0153.0",
|
||||
"0.153.0-alpha.01",
|
||||
"0.153.0-alpha..1",
|
||||
] {
|
||||
assert!(!is_official_server_older(version, "0.152.0"));
|
||||
assert!(!is_official_server_older("0.153.0", version));
|
||||
for release in ["0.153.0", "0.153.0-alpha.10", "0.0.0"] {
|
||||
assert_eq!(server_version_notice_kind(version, release), None);
|
||||
assert_eq!(server_version_notice_kind(release, version), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user