mirror of
https://github.com/openai/codex.git
synced 2026-10-26 18:38:47 +00:00
## Why MCP inventory can be cached or collected separately from a thread's live connections, so tool availability alone does not describe the current runtime state. ## What changed - Add a nullable `runtimeStatus` to `mcpServerStatus/list` for thread-scoped requests, covering not-started, starting, connected, authentication-required, failed, cancelled, and disabled connections. - Observe published connection state without starting or reconnecting servers, and return an unknown status when no thread is supplied or the active configuration no longer matches the published registration. - Show connection state and tool counts in the compact `/mcp` view while retaining the detailed inventory in `/mcp verbose` and compatibility with servers that omit `runtimeStatus`. ## Testing - Cover runtime status transitions, deferred and disabled servers, closed transports, configuration changes, protocol compatibility, and TUI rendering. GitOrigin-RevId: e3bb6efe652f0fa8b3c97d5c53e4729b3a87cd91
237 lines
7.7 KiB
Rust
237 lines
7.7 KiB
Rust
use crate::color::blend;
|
|
use crate::color::is_light;
|
|
use crate::terminal_palette::StdoutColorLevel;
|
|
use crate::terminal_palette::best_color;
|
|
use crate::terminal_palette::default_bg;
|
|
use crate::terminal_palette::default_fg;
|
|
use crate::terminal_palette::effective_stdout_color_level;
|
|
use crate::terminal_palette::rgb_color;
|
|
use crate::terminal_palette::stdout_color_level;
|
|
use ratatui::style::Color;
|
|
use ratatui::style::Style;
|
|
|
|
const LIGHT_BG_ACCENT_RGB: (u8, u8, u8) = (0, 95, 135);
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub(crate) enum StatusTone {
|
|
Success,
|
|
Attention,
|
|
Failure,
|
|
}
|
|
|
|
/// Semantic status colors that preserve the terminal's configured palette.
|
|
pub(crate) fn status_style(tone: StatusTone) -> Style {
|
|
status_style_for(tone, default_bg(), effective_stdout_color_level())
|
|
}
|
|
|
|
fn status_style_for(
|
|
tone: StatusTone,
|
|
terminal_bg: Option<(u8, u8, u8)>,
|
|
color_level: StdoutColorLevel,
|
|
) -> Style {
|
|
let light = terminal_bg.is_some_and(is_light);
|
|
let color = match (tone, color_level) {
|
|
(_, StdoutColorLevel::Unknown) => Color::Reset,
|
|
(StatusTone::Success, _) => Color::Green,
|
|
(StatusTone::Failure, _) => Color::Red,
|
|
// Yellow can disappear on light themes; use it only with a known dark background.
|
|
(StatusTone::Attention, _) if light || terminal_bg.is_none() => Color::Reset,
|
|
(StatusTone::Attention, _) => Color::Yellow,
|
|
};
|
|
Style::default().fg(color).bold()
|
|
}
|
|
// Decorative table rules should remain visible without competing with cell content.
|
|
const TABLE_SEPARATOR_FG_ALPHA: f32 = 0.20;
|
|
|
|
pub fn user_message_style() -> Style {
|
|
user_message_style_for(default_bg())
|
|
}
|
|
|
|
pub fn proposed_plan_style() -> Style {
|
|
proposed_plan_style_for(default_bg())
|
|
}
|
|
|
|
/// Returns a low-contrast rule style for separators within markdown tables.
|
|
pub(crate) fn table_separator_style() -> Style {
|
|
table_separator_style_for(default_fg(), default_bg(), stdout_color_level())
|
|
}
|
|
|
|
/// Returns the shared accent style for active or selected TUI controls.
|
|
pub(crate) fn accent_style() -> Style {
|
|
accent_style_for(default_bg())
|
|
}
|
|
|
|
/// Returns the style for a user-authored message using the provided terminal background.
|
|
pub fn user_message_style_for(terminal_bg: Option<(u8, u8, u8)>) -> Style {
|
|
match terminal_bg {
|
|
Some(bg) => Style::default().bg(user_message_bg(bg)),
|
|
None => Style::default(),
|
|
}
|
|
}
|
|
|
|
pub fn proposed_plan_style_for(terminal_bg: Option<(u8, u8, u8)>) -> Style {
|
|
match terminal_bg {
|
|
Some(bg) => Style::default().bg(proposed_plan_bg(bg)),
|
|
None => Style::default(),
|
|
}
|
|
}
|
|
|
|
/// Returns the shared accent style for the provided terminal background.
|
|
pub(crate) fn accent_style_for(terminal_bg: Option<(u8, u8, u8)>) -> Style {
|
|
if terminal_bg.is_some_and(is_light) {
|
|
Style::default().fg(best_color(LIGHT_BG_ACCENT_RGB)).bold()
|
|
} else {
|
|
Style::default().fg(Color::Cyan).bold()
|
|
}
|
|
}
|
|
|
|
fn table_separator_style_for(
|
|
terminal_fg: Option<(u8, u8, u8)>,
|
|
terminal_bg: Option<(u8, u8, u8)>,
|
|
color_level: StdoutColorLevel,
|
|
) -> Style {
|
|
let (Some(fg), Some(bg)) = (terminal_fg, terminal_bg) else {
|
|
return Style::default().dim();
|
|
};
|
|
let separator_rgb = blend(fg, bg, TABLE_SEPARATOR_FG_ALPHA);
|
|
match color_level {
|
|
StdoutColorLevel::TrueColor => Style::default().fg(rgb_color(separator_rgb)),
|
|
StdoutColorLevel::Ansi256 => Style::default().fg(best_color(separator_rgb)),
|
|
StdoutColorLevel::Ansi16 | StdoutColorLevel::Unknown => Style::default().dim(),
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::disallowed_methods)]
|
|
pub fn user_message_bg(terminal_bg: (u8, u8, u8)) -> Color {
|
|
best_color(user_message_bg_rgb(terminal_bg))
|
|
}
|
|
|
|
pub(crate) fn user_message_bg_rgb(terminal_bg: (u8, u8, u8)) -> (u8, u8, u8) {
|
|
let (top, alpha) = if is_light(terminal_bg) {
|
|
((0, 0, 0), 0.04)
|
|
} else {
|
|
((255, 255, 255), 0.12)
|
|
};
|
|
blend(top, terminal_bg, alpha)
|
|
}
|
|
|
|
#[allow(clippy::disallowed_methods)]
|
|
pub fn proposed_plan_bg(terminal_bg: (u8, u8, u8)) -> Color {
|
|
user_message_bg(terminal_bg)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use pretty_assertions::assert_eq;
|
|
use ratatui::style::Modifier;
|
|
|
|
#[test]
|
|
fn status_colors_preserve_light_terminal_themes() {
|
|
for level in [StdoutColorLevel::TrueColor, StdoutColorLevel::Ansi256] {
|
|
for bg in [(255, 255, 255), (130, 130, 130), (220, 210, 180)] {
|
|
for (tone, color) in [
|
|
(StatusTone::Success, Color::Green),
|
|
(StatusTone::Attention, Color::Reset),
|
|
(StatusTone::Failure, Color::Red),
|
|
] {
|
|
assert_eq!(
|
|
status_style_for(tone, Some(bg), level),
|
|
Style::default().fg(color).bold(),
|
|
);
|
|
}
|
|
}
|
|
for bg in [(0, 0, 0), (0, 218, 0)] {
|
|
assert_eq!(
|
|
status_style_for(StatusTone::Attention, Some(bg), level),
|
|
Style::default().fg(Color::Yellow).bold(),
|
|
);
|
|
}
|
|
assert_eq!(
|
|
status_style_for(StatusTone::Attention, /*terminal_bg*/ None, level),
|
|
Style::default().fg(Color::Reset).bold(),
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn status_colors_preserve_ansi16_and_no_color_fallbacks() {
|
|
for (tone, light, dark) in [
|
|
(StatusTone::Success, Color::Green, Color::Green),
|
|
(StatusTone::Attention, Color::Reset, Color::Yellow),
|
|
(StatusTone::Failure, Color::Red, Color::Red),
|
|
] {
|
|
for (bg, expected) in [((255, 255, 255), light), ((0, 0, 0), dark)] {
|
|
assert_eq!(
|
|
status_style_for(tone, Some(bg), StdoutColorLevel::Ansi16),
|
|
Style::default().fg(expected).bold()
|
|
);
|
|
assert_eq!(
|
|
status_style_for(tone, Some(bg), StdoutColorLevel::Unknown),
|
|
Style::default().fg(Color::Reset).bold()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn accent_style_uses_darker_cyan_on_light_backgrounds() {
|
|
let style = accent_style_for(Some((255, 255, 255)));
|
|
|
|
assert_eq!(style.fg, Some(best_color(LIGHT_BG_ACCENT_RGB)));
|
|
assert!(style.add_modifier.contains(Modifier::BOLD));
|
|
}
|
|
|
|
#[test]
|
|
fn accent_style_uses_cyan_on_dark_or_unknown_backgrounds() {
|
|
let expected = Style::default().fg(Color::Cyan).bold();
|
|
|
|
assert_eq!(accent_style_for(Some((0, 0, 0))), expected);
|
|
assert_eq!(accent_style_for(/*terminal_bg*/ None), expected);
|
|
}
|
|
|
|
#[test]
|
|
fn table_separator_blends_toward_dark_background() {
|
|
let style = table_separator_style_for(
|
|
Some((255, 255, 255)),
|
|
Some((0, 0, 0)),
|
|
StdoutColorLevel::TrueColor,
|
|
);
|
|
|
|
assert_eq!(style.fg, Some(rgb_color((51, 51, 51))));
|
|
}
|
|
|
|
#[test]
|
|
fn table_separator_blends_toward_light_background() {
|
|
let style = table_separator_style_for(
|
|
Some((0, 0, 0)),
|
|
Some((255, 255, 255)),
|
|
StdoutColorLevel::TrueColor,
|
|
);
|
|
|
|
assert_eq!(style.fg, Some(rgb_color((204, 204, 204))));
|
|
}
|
|
|
|
#[test]
|
|
fn table_separator_dims_when_palette_aware_color_is_unavailable() {
|
|
let expected = Style::default().dim();
|
|
|
|
assert_eq!(
|
|
table_separator_style_for(
|
|
Some((255, 255, 255)),
|
|
Some((0, 0, 0)),
|
|
StdoutColorLevel::Ansi16,
|
|
),
|
|
expected
|
|
);
|
|
assert_eq!(
|
|
table_separator_style_for(
|
|
/*terminal_fg*/ None,
|
|
Some((0, 0, 0)),
|
|
StdoutColorLevel::TrueColor,
|
|
),
|
|
expected
|
|
);
|
|
}
|
|
}
|