Synced from monorepo

Changes:
- Stop hooks for session lifecycle
- Add x.ai/session/state and x.ai/session/import ACP methods
- Deny-and-continue for auto-mode classifier blocks with denial limits
- Drop codebase-upload from dhat soak test
- scheduler_create upsert via task_id; retire one-shot tasks
- Clipboard: copy file fallback + honest toasts for SSH/Apple Terminal
- Polarity-safe syntax colors in minimal mode
- Auto mode classifies unvetted env prefixes instead of hard-prompting
- Add GROK_CLIPBOARD_NO_OSC52 kill switch to force OSC 52 off
This commit is contained in:
grokkybara[bot] 2026-07-19 18:40:33 +01:00
commit ba76b0a683
143 changed files with 9465 additions and 3419 deletions

View file

@ -133,12 +133,32 @@ fn terminal_supports_truecolor() -> bool {
/// `ColorLevel` declaration-order discriminant.
static COLOR_LEVEL_CAP: AtomicU8 = AtomicU8::new(ColorLevel::TrueColor as u8);
/// When set, RGB syntax colors are remapped with [`polarity_safe_syntax_ansi`]
/// instead of nearest-ANSI16. Used by pager minimal mode: the canvas is the
/// terminal's own bg, so night-theme pastels quantized to White vanish on
/// light profiles. See `xai-grok-pager-render` syntax docs.
static POLARITY_SAFE_SYNTAX: AtomicU8 = AtomicU8::new(0);
/// Set the process-wide upper bound on the effective color level. Pass
/// [`ColorLevel::TrueColor`] to remove the cap.
pub fn set_color_level_cap(cap: ColorLevel) {
COLOR_LEVEL_CAP.store(cap as u8, Ordering::Relaxed);
}
/// Engage dual-polarity-safe syntax color remapping (minimal / terminal-native).
///
/// When enabled, [`adapt_color`] maps near-gray RGB to "no color" (inherit
/// terminal default fg) and chromatic RGB to base ANSI accents — never White.
pub fn set_polarity_safe_syntax(enabled: bool) {
POLARITY_SAFE_SYNTAX.store(u8::from(enabled), Ordering::Relaxed);
}
/// Whether polarity-safe syntax remapping is active.
#[must_use]
pub fn polarity_safe_syntax() -> bool {
POLARITY_SAFE_SYNTAX.load(Ordering::Relaxed) != 0
}
fn color_level_cap() -> ColorLevel {
match COLOR_LEVEL_CAP.load(Ordering::Relaxed) {
0 => ColorLevel::None,
@ -169,7 +189,14 @@ pub fn set_color_level(level: ColorLevel) -> Result<(), ColorLevel> {
/// - 256-color terminals: RGB colors are converted to closest ANSI 256 color
/// - Basic terminals: colors are converted to closest ANSI 16 color
/// - No color: returns None
///
/// When [`polarity_safe_syntax`] is enabled (minimal mode), RGB tokens take
/// the dual-polarity path instead of nearest-ANSI16.
pub fn adapt_color(color: Color) -> Option<Color> {
if polarity_safe_syntax() {
return adapt_color_polarity_safe(color);
}
let level = get_color_level();
match level {
@ -187,6 +214,106 @@ pub fn adapt_color(color: Color) -> Option<Color> {
}
}
/// Polarity-safe remap for syntax tokens painted on a transparent canvas.
///
/// - Near-gray RGB → `None` (inherit terminal default fg)
/// - Chromatic RGB → base ANSI Red/Green/Yellow/Blue/Magenta/Cyan
/// - Existing ANSI → demote bright white / white body slots to `None`; keep accents
fn adapt_color_polarity_safe(color: Color) -> Option<Color> {
match color {
Color::Rgb(rgb) => polarity_safe_syntax_ansi(rgb.0, rgb.1, rgb.2).map(Color::Ansi),
Color::Ansi256(idx) => {
// Expand xterm index to an approximate RGB then re-map.
let (r, g, b) = ansi256_to_rgb(idx.index());
polarity_safe_syntax_ansi(r, g, b).map(Color::Ansi)
}
Color::Ansi(ansi) => match ansi {
// Body-ish slots that flip polarity → inherit default fg.
AnsiColor::Black
| AnsiColor::White
| AnsiColor::BrightBlack
| AnsiColor::BrightWhite => None,
// Demote bright accents to base (brights can wash out on light).
AnsiColor::BrightRed => Some(Color::Ansi(AnsiColor::Red)),
AnsiColor::BrightGreen => Some(Color::Ansi(AnsiColor::Green)),
AnsiColor::BrightYellow => Some(Color::Ansi(AnsiColor::Yellow)),
AnsiColor::BrightBlue => Some(Color::Ansi(AnsiColor::Blue)),
AnsiColor::BrightMagenta => Some(Color::Ansi(AnsiColor::Magenta)),
AnsiColor::BrightCyan => Some(Color::Ansi(AnsiColor::Cyan)),
other => Some(Color::Ansi(other)),
},
}
}
/// Dual-polarity-safe ANSI mapping for syntax tokens (minimal mode).
///
/// Returns `None` for near-gray (caller inherits terminal default fg).
/// Chromatic hues map to base ANSI colors only — never White/Black.
pub fn polarity_safe_syntax_ansi(r: u8, g: u8, b: u8) -> Option<AnsiColor> {
let max = r.max(g).max(b) as i32;
let min = r.min(g).min(b) as i32;
let chroma = max - min;
if chroma < 40 {
return None;
}
let (ri, gi, bi) = (r as i32, g as i32, b as i32);
let h = if max == ri {
let mut h = (gi - bi) * 60 / chroma;
if h < 0 {
h += 360;
}
h
} else if max == gi {
(bi - ri) * 60 / chroma + 120
} else {
(ri - gi) * 60 / chroma + 240
};
// Magenta starts at 255° so Tokyo Night purple (#bb9af7, ~261°) lands
// Magenta rather than Blue; pure blues (~221°) stay Blue.
Some(match h {
0..30 | 330..=360 => AnsiColor::Red,
30..90 => AnsiColor::Yellow,
90..150 => AnsiColor::Green,
150..210 => AnsiColor::Cyan,
210..255 => AnsiColor::Blue,
_ => AnsiColor::Magenta,
})
}
/// Approximate RGB for an xterm 256-color index (cube + grayscale).
fn ansi256_to_rgb(idx: u8) -> (u8, u8, u8) {
match idx {
0 => (0, 0, 0),
1 => (128, 0, 0),
2 => (0, 128, 0),
3 => (128, 128, 0),
4 => (0, 0, 128),
5 => (128, 0, 128),
6 => (0, 128, 128),
7 => (192, 192, 192),
8 => (128, 128, 128),
9 => (255, 0, 0),
10 => (0, 255, 0),
11 => (255, 255, 0),
12 => (0, 0, 255),
13 => (255, 0, 255),
14 => (0, 255, 255),
15 => (255, 255, 255),
16..=231 => {
let n = idx - 16;
let r = n / 36;
let g = (n / 6) % 6;
let b = n % 6;
let level = |c: u8| if c == 0 { 0 } else { 55 + 40 * c };
(level(r), level(g), level(b))
}
232..=255 => {
let v = 8 + (idx - 232) * 10;
(v, v, v)
}
}
}
/// Convert an `anstyle::Style` to the appropriate color level.
pub fn adapt_style(style: anstyle::Style) -> anstyle::Style {
let fg = style.get_fg_color().and_then(adapt_color);
@ -284,4 +411,41 @@ mod tests {
assert!(ColorLevel::Basic < ColorLevel::Ansi256);
assert!(ColorLevel::Ansi256 < ColorLevel::TrueColor);
}
#[test]
fn polarity_safe_grays_inherit_default() {
assert_eq!(polarity_safe_syntax_ansi(0xc8, 0xc8, 0xc8), None);
assert_eq!(polarity_safe_syntax_ansi(0x6c, 0x6c, 0x6c), None);
}
#[test]
fn polarity_safe_never_white() {
for (r, g, b) in [
(0xbb, 0x9a, 0xf7),
(0x7d, 0xcf, 0xff),
(0x7a, 0xa2, 0xf7),
(0xff, 0x9e, 0x64),
(0xf7, 0x76, 0x8e),
(0xc8, 0xc8, 0xc8),
] {
let mapped = polarity_safe_syntax_ansi(r, g, b);
assert!(
!matches!(
mapped,
Some(AnsiColor::White | AnsiColor::BrightWhite | AnsiColor::Black)
),
"#{r:02x}{g:02x}{b:02x} -> {mapped:?}"
);
}
}
#[test]
fn adapt_color_polarity_safe_flag_drops_gray_rgb() {
set_polarity_safe_syntax(true);
let out = adapt_color(Color::Rgb(RgbColor(0xc8, 0xc8, 0xc8)));
assert_eq!(out, None, "gray body must inherit default fg");
let magenta = adapt_color(Color::Rgb(RgbColor(0xbb, 0x9a, 0xf7)));
assert_eq!(magenta, Some(Color::Ansi(AnsiColor::Magenta)));
set_polarity_safe_syntax(false);
}
}

View file

@ -47,7 +47,8 @@ mod url_scan;
pub use buffers::MarkdownBuffers;
pub use checkpoint::{Checkpoint, CheckpointKind};
pub use colors::{
ColorLevel, adapt_color, adapt_style, detect_color_level, get_color_level, set_color_level_cap,
ColorLevel, adapt_color, adapt_style, detect_color_level, get_color_level,
polarity_safe_syntax, polarity_safe_syntax_ansi, set_color_level_cap, set_polarity_safe_syntax,
};
pub use latex_delimiters::{LatexDelimiterNormalizer, normalize_latex_delimiters};
pub use output::{CodeBlockSpan, HyperlinkTarget, MarkdownRenderOutput, MarkdownRenderView};