mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
Add hostname to the configurable TUI status line (#39795)
## What changed - Add `hostname` as a selectable status-line item and show it in setup previews. - Read the normalized operating-system hostname without triggering DNS resolution, and omit the item when no hostname is available. ## Testing - Cover hostname normalization, status-line rendering, and setup and surface previews. GitOrigin-RevId: c5e4e0ee1dba6e4bfdf942562a6b037835ff9e69
This commit is contained in:
@@ -9,6 +9,10 @@ use winapi_util::sysinfo::ComputerNameKind;
|
||||
use winapi_util::sysinfo::get_computer_name;
|
||||
|
||||
static HOST_NAME: LazyLock<Option<String>> = LazyLock::new(compute_host_name);
|
||||
static OS_HOST_NAME: LazyLock<Option<String>> = LazyLock::new(|| {
|
||||
let kernel_hostname = gethostname::gethostname();
|
||||
normalize_host_name(&kernel_hostname.to_string_lossy())
|
||||
});
|
||||
|
||||
/// Returns a process-cached canonical hostname, falling back to the normalized
|
||||
/// kernel hostname. The first call on Unix may perform blocking DNS resolution.
|
||||
@@ -16,9 +20,13 @@ pub fn host_name() -> Option<String> {
|
||||
HOST_NAME.clone()
|
||||
}
|
||||
|
||||
/// Returns the process-cached operating-system hostname without DNS resolution.
|
||||
pub fn os_host_name() -> Option<String> {
|
||||
OS_HOST_NAME.clone()
|
||||
}
|
||||
|
||||
fn compute_host_name() -> Option<String> {
|
||||
let kernel_hostname = gethostname::gethostname();
|
||||
let kernel_hostname = normalize_host_name(&kernel_hostname.to_string_lossy())?;
|
||||
let kernel_hostname = os_host_name()?;
|
||||
|
||||
// Remote sandbox requirements are meant to target remote hosts by DNS name,
|
||||
// so prefer the canonical FQDN when the local resolver can provide one.
|
||||
@@ -74,8 +82,20 @@ fn normalize_fqdn_candidate(hostname: &str) -> Option<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::normalize_fqdn_candidate;
|
||||
use super::normalize_host_name;
|
||||
use super::os_host_name;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn os_host_name_matches_normalized_kernel_hostname() {
|
||||
let kernel_hostname = gethostname::gethostname();
|
||||
|
||||
assert_eq!(
|
||||
os_host_name(),
|
||||
normalize_host_name(&kernel_hostname.to_string_lossy())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_fqdn_candidate_accepts_dns_qualified_name() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -120,6 +120,7 @@ pub use hook_config::HooksToml;
|
||||
pub use hook_config::ManagedHooksRequirementsToml;
|
||||
pub use hook_config::MatcherGroup;
|
||||
pub use host_name::host_name;
|
||||
pub use host_name::os_host_name;
|
||||
pub use in_app_browser_requirements::InAppBrowserRequirementsToml;
|
||||
pub use marketplace_edit::MarketplaceConfigUpdate;
|
||||
pub use marketplace_edit::RemoveMarketplaceConfigOutcome;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
source: tui/src/bottom_pane/status_line_setup.rs
|
||||
expression: "render_lines(&view,\n100).lines().map(str::trim_end).collect::<Vec<_>>().join(\"\\n\")"
|
||||
---
|
||||
|
||||
Configure Status Line
|
||||
Select which items to display in the status line.
|
||||
|
||||
Type to search
|
||||
>
|
||||
› [x] Use theme colors Apply colors from the active /theme
|
||||
───────────────────────
|
||||
[x] hostname Current machine hostname (omitted when unavailable)
|
||||
[x] current-dir Current working directory
|
||||
[ ] model Current model name
|
||||
[ ] model-with-reasoning Current model name with reasoning level
|
||||
[ ] reasoning Current reasoning level
|
||||
[ ] project-name Project name (omitted when unavailable)
|
||||
|
||||
ssh-build-01.example.com · ~/codex-rs
|
||||
Press space to toggle; ←/→ to move; enter to confirm and close; esc to close
|
||||
@@ -11,6 +11,7 @@
|
||||
//!
|
||||
//! - Model information (name, reasoning level)
|
||||
//! - Directory paths (current dir, project root)
|
||||
//! - Machine hostname
|
||||
//! - Git information (branch name)
|
||||
//! - Permissions profile
|
||||
//! - Approval mode
|
||||
@@ -74,6 +75,9 @@ pub(crate) enum StatusLineItem {
|
||||
)]
|
||||
ProjectRoot,
|
||||
|
||||
/// Hostname of the machine running Codex.
|
||||
Hostname,
|
||||
|
||||
/// Current git branch name (if in a repository).
|
||||
GitBranch,
|
||||
|
||||
@@ -159,6 +163,7 @@ impl StatusLineItem {
|
||||
StatusLineItem::Reasoning => "Current reasoning level",
|
||||
StatusLineItem::CurrentDir => "Current working directory",
|
||||
StatusLineItem::ProjectRoot => "Project name (omitted when unavailable)",
|
||||
StatusLineItem::Hostname => "Current machine hostname (omitted when unavailable)",
|
||||
StatusLineItem::GitBranch => "Current Git branch (omitted when unavailable)",
|
||||
StatusLineItem::PullRequestNumber => {
|
||||
"Open pull request number for the current branch (omitted when unavailable)"
|
||||
@@ -216,6 +221,7 @@ impl StatusLineItem {
|
||||
StatusLineItem::Reasoning => StatusSurfacePreviewItem::Reasoning,
|
||||
StatusLineItem::CurrentDir => StatusSurfacePreviewItem::CurrentDir,
|
||||
StatusLineItem::ProjectRoot => StatusSurfacePreviewItem::ProjectRoot,
|
||||
StatusLineItem::Hostname => StatusSurfacePreviewItem::Hostname,
|
||||
StatusLineItem::GitBranch => StatusSurfacePreviewItem::GitBranch,
|
||||
StatusLineItem::PullRequestNumber => StatusSurfacePreviewItem::PullRequestNumber,
|
||||
StatusLineItem::BranchChanges => StatusSurfacePreviewItem::BranchChanges,
|
||||
@@ -726,6 +732,38 @@ mod tests {
|
||||
assert_snapshot!(render_lines(&view, /*width*/ 100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn setup_view_snapshot_includes_hostname() {
|
||||
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
|
||||
let view = StatusLineSetupView::new(
|
||||
Some(&[
|
||||
StatusLineItem::Hostname.to_string(),
|
||||
StatusLineItem::CurrentDir.to_string(),
|
||||
]),
|
||||
/*use_theme_colors*/ true,
|
||||
StatusSurfacePreviewData::from_iter([
|
||||
(
|
||||
StatusLineItem::Hostname.preview_item(),
|
||||
"ssh-build-01.example.com".to_string(),
|
||||
),
|
||||
(
|
||||
StatusLineItem::CurrentDir.preview_item(),
|
||||
"~/codex-rs".to_string(),
|
||||
),
|
||||
]),
|
||||
AppEventSender::new(tx_raw),
|
||||
crate::keymap::RuntimeKeymap::defaults().list,
|
||||
);
|
||||
|
||||
assert_snapshot!(
|
||||
render_lines(&view, /*width*/ 100)
|
||||
.lines()
|
||||
.map(str::trim_end)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
fn render_lines(view: &StatusLineSetupView, width: u16) -> String {
|
||||
let height = view.desired_height(width);
|
||||
let area = Rect::new(0, 0, width, height);
|
||||
|
||||
@@ -47,7 +47,9 @@ impl StatusLineAccent {
|
||||
| StatusLineItem::ThreadCredits
|
||||
| StatusLineItem::EstimatedThreadCost => Self::Usage,
|
||||
StatusLineItem::FiveHourLimit | StatusLineItem::WeeklyLimit => Self::Limit,
|
||||
StatusLineItem::CodexVersion | StatusLineItem::SessionId => Self::Metadata,
|
||||
StatusLineItem::CodexVersion | StatusLineItem::Hostname | StatusLineItem::SessionId => {
|
||||
Self::Metadata
|
||||
}
|
||||
StatusLineItem::FastMode | StatusLineItem::RawOutput => Self::Mode,
|
||||
StatusLineItem::Permissions => Self::Mode,
|
||||
StatusLineItem::ApprovalMode => Self::Mode,
|
||||
|
||||
@@ -11,6 +11,7 @@ pub(crate) enum StatusSurfacePreviewItem {
|
||||
ProjectName,
|
||||
ProjectRoot,
|
||||
CurrentDir,
|
||||
Hostname,
|
||||
Status,
|
||||
ThreadTitle,
|
||||
GitBranch,
|
||||
@@ -46,6 +47,7 @@ impl StatusSurfacePreviewItem {
|
||||
StatusSurfacePreviewItem::ProjectName => "my-project",
|
||||
StatusSurfacePreviewItem::ProjectRoot => "my-project",
|
||||
StatusSurfacePreviewItem::CurrentDir => "~/my-project/subdir",
|
||||
StatusSurfacePreviewItem::Hostname => "my-host",
|
||||
StatusSurfacePreviewItem::Status => "Working",
|
||||
StatusSurfacePreviewItem::ThreadTitle => "thread title",
|
||||
StatusSurfacePreviewItem::GitBranch => "feat/awesome-feature",
|
||||
@@ -81,6 +83,7 @@ impl StatusSurfacePreviewItem {
|
||||
Self::ProjectName,
|
||||
Self::ProjectRoot,
|
||||
Self::CurrentDir,
|
||||
Self::Hostname,
|
||||
Self::Status,
|
||||
Self::ThreadTitle,
|
||||
Self::GitBranch,
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::status::format_estimated_usd_micros;
|
||||
use crate::status::format_tokens_compact;
|
||||
use codex_app_server_protocol::AskForApproval;
|
||||
use codex_config::ConfigLayerSource;
|
||||
use codex_config::os_host_name;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
@@ -682,6 +683,7 @@ impl ChatWidget {
|
||||
))
|
||||
}
|
||||
StatusLineItem::ProjectRoot => self.status_line_project_root_name(),
|
||||
StatusLineItem::Hostname => os_host_name(),
|
||||
StatusLineItem::GitBranch => self.status_line_branch.clone(),
|
||||
StatusLineItem::PullRequestNumber => self
|
||||
.status_line_git_summary
|
||||
@@ -800,6 +802,7 @@ impl ChatWidget {
|
||||
StatusSurfacePreviewItem::Status => return Some(self.run_state_status_text()),
|
||||
StatusSurfacePreviewItem::TaskProgress => return self.terminal_title_task_progress(),
|
||||
StatusSurfacePreviewItem::CurrentDir => StatusLineItem::CurrentDir,
|
||||
StatusSurfacePreviewItem::Hostname => StatusLineItem::Hostname,
|
||||
StatusSurfacePreviewItem::ThreadTitle => StatusLineItem::ThreadTitle,
|
||||
StatusSurfacePreviewItem::GitBranch => StatusLineItem::GitBranch,
|
||||
StatusSurfacePreviewItem::PullRequestNumber => StatusLineItem::PullRequestNumber,
|
||||
|
||||
@@ -2805,6 +2805,21 @@ async fn status_line_invalid_items_warn_once() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_line_hostname_renders_current_machine_hostname() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.thread_id = Some(ThreadId::new());
|
||||
chat.config.tui_status_line = Some(vec!["hostname".to_string()]);
|
||||
|
||||
chat.refresh_status_line();
|
||||
|
||||
assert_eq!(status_line_text(&chat), codex_config::os_host_name());
|
||||
assert!(
|
||||
drain_insert_history(&mut rx).is_empty(),
|
||||
"hostname should be accepted as a status line item"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_line_context_used_renders_labeled_percent() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
|
||||
@@ -85,6 +85,16 @@ fn cache_rate_limit_snapshot(chat: &mut ChatWidget) {
|
||||
}));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_surface_hostname_preview_uses_current_machine_hostname() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
|
||||
assert_eq!(
|
||||
status_preview_line(&mut chat, &[StatusLineItem::Hostname]),
|
||||
codex_config::os_host_name().expect("machine hostname")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_surface_preview_lines_live_only_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
|
||||
Reference in New Issue
Block a user