grok-build-upstream-mirror/crates/codegen/xai-grok-pager/src/diagnostics/view.rs
grokkybara[bot] 3af4d5d398 Synced from monorepo
Synced from monorepo

Changes:
- Shell: accept target response id on rewind execute
- Shell: stamp response id on chat user message chunks
- Worktree: optional rebuild and stale git registration cleanup in auto-GC
- Worktree: kind-aware auto-GC TTLs and config knobs
- Worktree: macOS process CWD scan and Unix PID liveness for GC guards
- Worktree: automatic throttled GC on startup (Linux age-based; non-Linux dead-only)
- Pager: add `[ui].combine_queued_prompts` to batch queued follow-ups
- Shell: stop overwriting user skills
- Tools: read markdown in `skills/` directories untruncated
- `/usage` shows per-session token and dollar usage in the TUI
- Security: prompt on environment-dumping `ps` variants
- Security: always-safe `kubectl` no longer runs arbitrary kubeconfig credential plugins without permission
- Tools: make scheduler deletion durable
- Shell: add relocation storage primitives
- Shell: give side model calls their own conversation ids
- Fix five workflow-runtime bugs (budget, pause, cancel, reconnect)
- Security: peel `env -S` / `--split-string` operands in the Bash permission gate (managed deny/ask)
- Pager: expose doctor in the TUI
- Security: block unauthorized RCE via abused safe commands
- Pager idle watcher cue: "1 subagent still running" instead of "watching · 1 subagent"
- Security: block `rg --pre` arbitrary code execution in auto-mode
- Voice: diagnose silent-mic failures (macOS permission) and add doctor/terminal-setup Voice section
- App builder deployer: `allow_forking` and `show_built_with_grok`
- Pager: stop stacking duplicate "Worked for" markers on parked turns
- Shell: support `max` as a distinct reasoning effort tier
- Tools: serialize background `/loop` fires on the whole work unit
- Shell: add working-directory relocation state primitives
- Proto: `ClientToolResult` and `ChatConfig` client-side tools
- Shell: model providers
- Chat: select App Builder product on the Build path
- Shell: attach author identity to feedback when the deployment opts in
- Doctor: fix for SSH wrap setup
- Workflow authoring skills: create-workflow and import-claude-workflow docs
- Add read-only grok doctor
- Sandbox: apply Landlock without a controlling TTY
- Pager: recover image paste over grok wrap on headless remotes
- Pager: make actions screen-mode aware
- Shell: resume sessions when the working directory moves
- Pager: centralize terminal diagnostics
- Workspace: gate inline shell file access
- Pager: centralize terminal probes
- Pager: edit minimal prompts in an external editor
- Pager: standardize backgrounding on Ctrl+B
- Shell: recap rides the parent turn's prompt cache
- Tools: add scheduler lifecycle version clock

Source-Revision: 0f4d7c91b8b2b408333f6de1e8a76cb8eaa71899
2026-07-21 18:10:23 +00:00

424 lines
15 KiB
Rust

//! Interpretation of terminal probe snapshots.
use crate::diagnostics::probes::{
CommonProbeSnapshot, DiagnosticRuntimeEvidence, DoctorProbeSnapshot, ProbeSnapshot,
RuntimeEvidence, StandaloneDiagnosticSnapshot, TmuxProbeResult,
};
use crate::diagnostics::{
ClipboardFacts, ColorFacts, DataControlFact, DiagnosticFacts, DiagnosticFinding, DiagnosticId,
DiagnosticReport, FindingDisposition, KeyboardFact, ManualRemediation, NewlineFact, ProbeNote,
ProbeStatus, RuntimeFact, TerminalWarning, WarningCategory,
};
use crate::terminal::TerminalName;
pub struct DiagnosticSnapshot<'a> {
pub common: CommonProbeSnapshot<'a>,
pub clipboard: crate::diagnostics::probes::ClipboardProbeFacts,
pub host_os: crate::host::HostOs,
pub display_server: crate::host::DisplayServer,
pub container_no_display: bool,
pub color_level: RuntimeEvidence<crate::theme::color_support::ColorLevel>,
pub runtime: DiagnosticRuntimeEvidence<'a>,
}
impl<'a> DiagnosticSnapshot<'a> {
pub fn from_parts(
common: ProbeSnapshot<'a>,
clipboard: crate::diagnostics::probes::ClipboardProbeFacts,
host_os: crate::host::HostOs,
display_server: crate::host::DisplayServer,
container_no_display: bool,
color_level: crate::theme::color_support::ColorLevel,
runtime: DiagnosticRuntimeEvidence<'a>,
) -> Self {
Self {
common: CommonProbeSnapshot {
terminal: common.terminal,
tmux: common.tmux,
wayland: common.wayland,
},
clipboard,
host_os,
display_server,
container_no_display,
color_level: RuntimeEvidence::Available(color_level),
runtime,
}
}
}
impl<'a> From<DoctorProbeSnapshot<'a>> for DiagnosticSnapshot<'a> {
fn from(snapshot: DoctorProbeSnapshot<'a>) -> Self {
let runtime = snapshot.common.runtime.into();
Self {
common: CommonProbeSnapshot {
terminal: snapshot.common.terminal,
tmux: snapshot.common.tmux,
wayland: snapshot.common.wayland,
},
clipboard: snapshot.clipboard,
host_os: snapshot.host_os,
display_server: snapshot.display_server,
container_no_display: snapshot.container_no_display,
color_level: RuntimeEvidence::Available(snapshot.color_level),
runtime,
}
}
}
impl<'a> From<StandaloneDiagnosticSnapshot<'a>> for DiagnosticSnapshot<'a> {
fn from(snapshot: StandaloneDiagnosticSnapshot<'a>) -> Self {
Self {
common: snapshot.common,
clipboard: snapshot.clipboard,
host_os: snapshot.host_os,
display_server: snapshot.display_server,
container_no_display: snapshot.container_no_display,
color_level: snapshot.color_level,
runtime: DiagnosticRuntimeEvidence {
fullscreen_active: RuntimeEvidence::Unavailable,
kitty_flags_pushed: RuntimeEvidence::Unavailable,
xtversion: RuntimeEvidence::Unavailable,
},
}
}
}
pub fn view(snapshot: DiagnosticSnapshot<'_>) -> DiagnosticReport {
let ctx = snapshot.common.terminal;
let wezterm_warning = wezterm_warning(&snapshot);
let suppress_newline = wezterm_warning.is_some()
|| matches!(
snapshot.runtime.kitty_flags_pushed,
RuntimeEvidence::Unavailable
) && super::wezterm_shape(
snapshot.common.terminal,
runtime_xtversion(snapshot.runtime.xtversion),
)
.is_some();
let mut warnings = startup_warnings(&snapshot);
warnings.extend(super::diagnose_wayland_data_control_from_common(
&snapshot.common,
));
warnings.extend(wezterm_warning);
if let RuntimeEvidence::Available(color_level) = snapshot.color_level {
warnings.extend(super::color_support_warning(
color_level,
ctx.brand,
ctx.is_tmux_backed(),
&ctx.tmux_config_path(),
));
}
let mut findings = warnings.into_iter().filter_map(issue).collect::<Vec<_>>();
findings.extend(
super::ssh_wrap_hint(
ctx.is_ssh,
snapshot.clipboard.osc52_sink_active,
ctx.is_official_vscode_remote,
)
.and_then(recommendation),
);
DiagnosticReport {
facts: facts(&snapshot, suppress_newline),
findings,
probe_notes: probe_notes(&snapshot),
}
}
fn startup_warnings(snapshot: &DiagnosticSnapshot<'_>) -> Vec<TerminalWarning> {
let fullscreen_active = match snapshot.runtime.fullscreen_active {
RuntimeEvidence::Available(fullscreen_active) => Some(fullscreen_active),
RuntimeEvidence::Unavailable => None,
};
super::collect_startup_warnings_from(
snapshot.common.terminal,
&snapshot.common.tmux,
fullscreen_active,
)
}
fn wezterm_warning(snapshot: &DiagnosticSnapshot<'_>) -> Option<TerminalWarning> {
let RuntimeEvidence::Available(kitty_flags_pushed) = snapshot.runtime.kitty_flags_pushed else {
return None;
};
super::wezterm_kitty_keyboard_warning_from(
snapshot.common.terminal,
kitty_flags_pushed,
runtime_xtversion(snapshot.runtime.xtversion),
)
}
fn runtime_xtversion(evidence: RuntimeEvidence<Option<&str>>) -> Option<&str> {
match evidence {
RuntimeEvidence::Available(xtversion) => xtversion,
RuntimeEvidence::Unavailable => None,
}
}
fn facts(snapshot: &DiagnosticSnapshot<'_>, suppress_newline: bool) -> DiagnosticFacts {
let ctx = snapshot.common.terminal;
let available_themes = match snapshot.color_level {
RuntimeEvidence::Available(color_level) => crate::theme::ThemeKind::ALL
.iter()
.copied()
.filter(|kind| color_level.has_truecolor() || !kind.requires_truecolor())
.collect(),
RuntimeEvidence::Unavailable => Vec::new(),
};
let keyboard_capabilities =
crate::terminal::keyboard_capabilities_for_host(ctx.brand, snapshot.host_os);
let keyboard = (keyboard_capabilities
.modifier_delivery
.benefits_from_rescue()
|| keyboard_capabilities.enter_needs_rescue())
.then_some(KeyboardFact {
modifier_delivery: keyboard_capabilities.modifier_delivery,
os: snapshot.host_os,
});
let newline = (ctx.shift_enter_unavailable() && !suppress_newline).then(|| {
if ctx.vte_version.is_some() || ctx.brand == TerminalName::Vte {
NewlineFact::Vte {
version: ctx.vte_version.clone(),
}
} else if ctx.brand.is_vscode_family() {
NewlineFact::XtermJs {
terminal: ctx.brand,
}
} else {
NewlineFact::NoKittyKeyboardProtocol
}
});
let data_control = if !snapshot.common.wayland.is_wayland {
DataControlFact::NotApplicable
} else {
match &snapshot.common.wayland.data_control {
TmuxProbeResult::Available(true) => DataControlFact::Available,
TmuxProbeResult::Available(false) => DataControlFact::Missing,
TmuxProbeResult::Unavailable | TmuxProbeResult::Unsupported => {
DataControlFact::Unavailable
}
TmuxProbeResult::Error(_) => DataControlFact::Error,
}
};
let clipboard = clipboard_facts(snapshot, data_control);
DiagnosticFacts {
terminal: ctx.brand,
xtversion: match snapshot.runtime.xtversion {
RuntimeEvidence::Available(Some(xtversion)) => {
RuntimeFact::Available(xtversion.to_owned())
}
RuntimeEvidence::Available(None) => RuntimeFact::NoReply,
RuntimeEvidence::Unavailable => RuntimeFact::Unavailable,
},
multiplexer: ctx.multiplexer,
byobu: ctx.byobu,
ssh: ctx.is_ssh,
color: ColorFacts {
level: match snapshot.color_level {
RuntimeEvidence::Available(level) => RuntimeFact::Available(level),
RuntimeEvidence::Unavailable => RuntimeFact::Unavailable,
},
available_themes,
total_themes: crate::theme::ThemeKind::ALL.len(),
},
keyboard,
newline,
clipboard,
voice: None,
}
}
fn clipboard_facts(
snapshot: &DiagnosticSnapshot<'_>,
data_control: DataControlFact,
) -> ClipboardFacts {
use crate::clipboard::{ClipboardDelivery, ClipboardEnvironment, expected_delivery};
let route = &snapshot.clipboard.route;
let environment = ClipboardEnvironment {
brand: snapshot.common.terminal.brand,
host_os: snapshot.host_os,
display_server: snapshot.display_server,
remote: snapshot.common.terminal.is_ssh,
container: snapshot.container_no_display,
osc52_sink: snapshot.clipboard.osc52_sink_active,
wayland_data_control: matches!(
snapshot.common.wayland.data_control,
TmuxProbeResult::Available(true)
),
wl_copy_available: snapshot.common.wayland.wl_copy_available,
};
let native_preflight = crate::clipboard::native_clipboard_preflight(route.native, environment);
let delivery = expected_delivery(
native_preflight,
route.tmux_buffer,
route.osc52,
environment,
);
let fix = match delivery {
ClipboardDelivery::Confirmed => None,
ClipboardDelivery::Unverified | ClipboardDelivery::Failed
if snapshot.common.terminal.is_ssh =>
{
Some("grok wrap <ssh command> or /minimal")
}
ClipboardDelivery::Unverified | ClipboardDelivery::Failed
if snapshot.container_no_display =>
{
Some("grok wrap <command> or /minimal")
}
ClipboardDelivery::Unverified => Some("grok wrap or /minimal"),
ClipboardDelivery::Failed => Some("/minimal"),
};
ClipboardFacts {
native_route: route.native,
native_tool: snapshot.clipboard.native_tool.to_owned(),
native_preflight,
tmux_route: route.tmux_buffer,
osc52_route: route.osc52,
osc52_capability: environment.osc52_capability(),
wrap_sink: snapshot.clipboard.osc52_sink_active,
display_server: snapshot.display_server,
container_no_display: snapshot.container_no_display,
data_control,
delivery,
fix: fix.map(str::to_owned),
}
}
fn issue(warning: TerminalWarning) -> Option<DiagnosticFinding> {
finding(warning, FindingDisposition::Issue)
}
fn recommendation(warning: TerminalWarning) -> Option<DiagnosticFinding> {
finding(warning, FindingDisposition::Recommendation)
}
fn finding(warning: TerminalWarning, disposition: FindingDisposition) -> Option<DiagnosticFinding> {
let id = id_for(warning.category)?;
Some(DiagnosticFinding {
id,
disposition,
message: warning.message,
remediation: warning.fix.map(|fix| ManualRemediation {
fix,
config_path: warning.config_path,
}),
automatic_remediation: (id == crate::diagnostics::SSH_WRAP_ID)
.then(crate::diagnostics::ssh_wrap_automatic_remediation),
note: warning.note,
})
}
pub(crate) const fn id_for(category: WarningCategory) -> Option<DiagnosticId> {
let item = match category {
WarningCategory::Clipboard => "tmux-clipboard",
WarningCategory::DcsPassthrough => "dcs-passthrough",
WarningCategory::ControlMode => "control-mode",
WarningCategory::ByobuScreen => "byobu-screen",
WarningCategory::UnsupportedTerminal => "unsupported-emulator",
WarningCategory::TmuxExtendedKeysOff => "tmux-extended-keys",
WarningCategory::WaylandNoDataControl => "wayland-data-control",
WarningCategory::WezTermKittyKeyboardOff => "wezterm-kitty",
WarningCategory::LimitedColorSupport => "limited-color",
WarningCategory::SshWithoutWrap => "ssh-wrap",
WarningCategory::NotificationProtocolFallback
| WarningCategory::FocusTrackingUnavailable
| WarningCategory::SandboxProfileConflict => return None,
};
Some(DiagnosticId::new("terminal", item))
}
fn probe_notes(snapshot: &DiagnosticSnapshot<'_>) -> Vec<ProbeNote> {
let mut notes = Vec::new();
if snapshot.common.terminal.is_tmux_backed() {
probe_note(&mut notes, "tmux.version", &snapshot.common.tmux.version);
probe_note(
&mut notes,
"tmux.extended-keys",
&snapshot.common.tmux.extended_keys,
);
probe_note(
&mut notes,
"tmux.set-clipboard",
&snapshot.common.tmux.set_clipboard,
);
probe_note(
&mut notes,
"tmux.allow-passthrough-support",
&snapshot.common.tmux.allow_passthrough_support,
);
if matches!(
snapshot.common.tmux.allow_passthrough_support,
TmuxProbeResult::Available(())
) {
probe_note(
&mut notes,
"tmux.allow-passthrough",
&snapshot.common.tmux.allow_passthrough,
);
}
probe_note(
&mut notes,
"tmux.control-mode",
&snapshot.common.tmux.control_mode,
);
}
runtime_probe_note(
&mut notes,
"runtime.fullscreen-active",
snapshot.runtime.fullscreen_active,
);
runtime_probe_note(
&mut notes,
"runtime.kitty-flags-pushed",
snapshot.runtime.kitty_flags_pushed,
);
runtime_probe_note(&mut notes, "runtime.xtversion", snapshot.runtime.xtversion);
runtime_probe_note(&mut notes, "terminal.color", snapshot.color_level);
if snapshot.common.wayland.is_wayland {
probe_note(
&mut notes,
"wayland.data-control",
&snapshot.common.wayland.data_control,
);
}
notes
}
fn probe_note<T>(notes: &mut Vec<ProbeNote>, probe: &'static str, result: &TmuxProbeResult<T>) {
let (status, message) = match result {
TmuxProbeResult::Available(_) => return,
TmuxProbeResult::Unsupported => (ProbeStatus::Unsupported, None),
TmuxProbeResult::Unavailable => (ProbeStatus::Unavailable, None),
TmuxProbeResult::Error(error) => (ProbeStatus::Error, Some(error.to_owned())),
};
notes.push(ProbeNote {
probe,
status,
message,
});
}
fn runtime_probe_note<T>(
notes: &mut Vec<ProbeNote>,
probe: &'static str,
evidence: RuntimeEvidence<T>,
) {
if matches!(evidence, RuntimeEvidence::Unavailable) {
notes.push(ProbeNote {
probe,
status: ProbeStatus::Unavailable,
message: None,
});
}
}
#[cfg(test)]
#[path = "view_tests.rs"]
mod tests;