Identify local background servers in /status (#46905)

## What changed

Rename the `/status` connection row from `Remote` to `Server`. For local daemon connections, display `Local background server` instead of the socket address and version. Keep the address and version for remote connections, including explicitly configured Unix sockets.

## Testing

Add a local background server snapshot and coverage distinguishing local daemon connections from remote Unix sockets. Update daemon startup and worktree tests to expect the new label.

GitOrigin-RevId: 9460415fe1ec16d90f575917c1a4ce1bf19faaaf
This commit is contained in:
Eric Traut
2026-09-20 23:23:13 +00:00
committed by copyberry
parent eaff9a5f14
commit 88ef37c649
10 changed files with 101 additions and 43 deletions
+1 -2
View File
@@ -117,7 +117,7 @@ async fn daemon_startup(command: &str) -> Result<()> {
let expected = if command == "start" {
// The draft header is visible before the session's command composer is ready.
steps.push_back(("GPT-5.6-Terra", b"/status\r"));
"app-server-control.sock"
"Server:Localbackgroundserver"
} else if bedrock_onboarding {
"UseAmazonBedrock"
} else {
@@ -177,7 +177,6 @@ async fn daemon_startup(command: &str) -> Result<()> {
output.clear();
} else if steps.is_empty() && text.contains(expected) {
if command == "start" {
ensure!(text.contains("unix://"));
ensure!(home.path().join("app-server-daemon/daemon.pid").exists());
} else if let Some(existing_daemon) = &existing_daemon {
ensure!(fs::read(&pid_file)? == *existing_daemon);
+1 -1
View File
@@ -498,7 +498,7 @@ trust_level = "trusted"
if backend == "daemon" && !analytics && matches!(observed, Ok(Ok(_))) {
session.writer_sender().send(b"/status\r".to_vec()).await?;
tokio::time::timeout(Duration::from_secs(/*secs*/ 10), async {
while !output.contains("app-server-control.sock") {
while !output.contains("Local background server") {
let bytes = stdout
.recv()
.await
@@ -82,6 +82,7 @@ async fn daemon_menu_is_read_only_and_confirmation_can_cancel_or_handoff() {
app.chat_widget.remote_connection = Some(RemoteConnectionStatus {
address: "local".into(),
version: "v0.153.0".into(),
is_local_daemon: true,
});
app.initialize_server_version_notice("0.154.0", Some("0.153.0"));
let view = app.agents_overview_view(Vec::new(), /*selected_thread_id*/ None);
@@ -213,6 +214,7 @@ async fn unavailable_daemon_menu_offers_guidance_without_update_actions() {
app.chat_widget.remote_connection = Some(RemoteConnectionStatus {
address: "ws://example.test:1234".into(),
version: "v0.153.0".into(),
is_local_daemon: false,
});
app.open_daemon_menu();
insta::assert_snapshot!(
@@ -155,6 +155,7 @@ async fn misalignment_policy_failure_stops_the_thread_and_renders_once() {
chat.remote_connection = Some(crate::status::remote_connection::RemoteConnectionStatus {
address: "wss://remote.example.com".to_string(),
version: "v1.0.0".to_string(),
is_local_daemon: false,
});
chat.show_misalignment_policy_precaution();
assert_chatwidget_snapshot!(
@@ -532,6 +532,7 @@ async fn status_command_requests_thread_usage_for_remote_connection_metadata() {
chat.remote_connection = Some(crate::status::remote_connection::RemoteConnectionStatus {
address: "wss://remote.example.com".to_string(),
version: "v1.0.0".to_string(),
is_local_daemon: false,
});
chat.dispatch_command(SlashCommand::Status);
+8 -6
View File
@@ -824,18 +824,20 @@ impl StatusHistoryCell {
lines.push(Line::from(Vec::<Span<'static>>::new()));
}
if let Some(remote_connection) = self.remote_connection.as_ref() {
let wrapped_remote = word_wrap_lines(
[Line::from(vec![
let value = if remote_connection.is_local_daemon {
Line::from("Local background server")
} else {
Line::from(vec![
Span::from(remote_connection.address.clone()),
Span::from(" (").dim(),
Span::from(remote_connection.version.clone()).dim(),
Span::from(")").dim(),
])],
RtOptions::new(value_width.max(1)),
);
])
};
let wrapped_remote = word_wrap_lines([value], RtOptions::new(value_width.max(1)));
let mut wrapped_remote = wrapped_remote.into_iter();
if let Some(first) = wrapped_remote.next() {
lines.push(formatter.line("Remote", first.spans));
lines.push(formatter.line("Server", first.spans));
lines.extend(wrapped_remote.map(|line| formatter.continuation(line.spans)));
}
lines.push(Line::from(Vec::<Span<'static>>::new()));
+21 -1
View File
@@ -9,6 +9,7 @@ use url::Url;
pub(crate) struct RemoteConnectionStatus {
pub(crate) address: String,
pub(crate) version: String,
pub(crate) is_local_daemon: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -40,7 +41,11 @@ pub(crate) fn remote_connection_status_value(
let version = server_version
.map(|version| format!("v{version}"))
.unwrap_or_else(|| "unknown".to_string());
Some(RemoteConnectionStatus { address, version })
Some(RemoteConnectionStatus {
address,
version,
is_local_daemon: matches!(app_server_target, AppServerTarget::LocalDaemon { .. }),
})
}
pub(crate) fn server_version_notice(client: &str, server: Option<&str>) -> Option<String> {
@@ -187,6 +192,7 @@ mod tests {
Some(RemoteConnectionStatus {
address: "ws://127.0.0.1:4500/".to_string(),
version: "v1.2.3".to_string(),
is_local_daemon: false,
})
);
@@ -202,6 +208,20 @@ mod tests {
Some(RemoteConnectionStatus {
address: format!("unix://{}", socket_path.display()),
version: "unknown".to_string(),
is_local_daemon: true,
})
);
let remote_socket_target = AppServerTarget::Remote {
endpoint: RemoteAppServerEndpoint::UnixSocket {
socket_path: socket_path.clone(),
},
};
assert_eq!(
remote_connection_status_value(&remote_socket_target, Some("1.2.3")),
Some(RemoteConnectionStatus {
address: format!("unix://{}", socket_path.display()),
version: "v1.2.3".to_string(),
is_local_daemon: false,
})
);
Ok(())
@@ -0,0 +1,23 @@
---
source: tui/src/status/tests.rs
expression: sanitized
---
/status
╭─────────────────────────────────────────────────────────────────────────────╮
│ >_ OpenAI Codex (v0.0.0) │
│ │
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
│ information on rate limits and credits │
│ │
│ Server: Local background server │
│ │
│ Model: gpt-5.1-codex-max (reasoning medium, summaries auto) │
│ Directory: [[workspace]] │
│ Permissions: Custom (workspace with network access, Ask for approval) │
│ Agents.md: <none> │
│ │
│ Token usage: 750 total (500 input + 250 output) │
│ Context window: 100% left (750 used / 272K) │
│ Limits: data not available yet │
╰─────────────────────────────────────────────────────────────────────────────╯
@@ -10,7 +10,7 @@ expression: sanitized
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
│ information on rate limits and credits │
│ │
│ Remote: unix:///tmp/codex-home/app-server-control/app-server- │
│ Server: unix:///tmp/codex-home/app-server-control/app-server- │
│ control.sock (v0.133.0) │
│ │
│ Model: gpt-5.1-codex-max (reasoning medium, summaries auto) │
+42 -32
View File
@@ -1638,41 +1638,51 @@ async fn status_snapshot_uses_default_reasoning_when_config_empty() {
.with_ymd_and_hms(2024, 2, 3, 4, 5, 6)
.single()
.expect("timestamp");
let remote_connection = RemoteConnectionStatus {
address: "unix:///tmp/codex-home/app-server-control/app-server-control.sock".to_string(),
version: "v0.133.0".to_string(),
};
for (is_local_daemon, snapshot) in [
(
false,
"status_snapshot_uses_default_reasoning_when_config_empty",
),
(true, "status_snapshot_local_background_server"),
] {
let remote_connection = RemoteConnectionStatus {
address: "unix:///tmp/codex-home/app-server-control/app-server-control.sock"
.to_string(),
version: "v0.133.0".to_string(),
is_local_daemon,
};
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let (composite, _) = new_status_output_with_rate_limits_handle(
&config,
/*requires_openai_auth*/ true,
/*model_provider_id*/ None,
Some(&remote_connection),
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
/*thread_name*/ None,
/*forked_from*/ None,
&[],
None,
now,
&model_slug,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ Some(Some(ReasoningEffort::Medium)),
"<none>".to_string(),
/*refreshing_rate_limits*/ false,
);
let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let (composite, _) = new_status_output_with_rate_limits_handle(
&config,
/*requires_openai_auth*/ true,
/*model_provider_id*/ None,
Some(&remote_connection),
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
/*thread_name*/ None,
/*forked_from*/ None,
&[],
None,
now,
&model_slug,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ Some(Some(ReasoningEffort::Medium)),
"<none>".to_string(),
/*refreshing_rate_limits*/ false,
);
let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(snapshot, sanitized);
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]