Synced from monorepo

Changes:
- Non-blocking coding-data sharing upsell banner
- Consolidate remediation in Doctor
- Auto mode defers fail-closed gate asks to the classifier
- Coalesce marketplace list fetches
- Allow removing a marketplace source by name
- Contain hung git marketplace sources (timeouts, non-blocking refresh, unbrick modal)
- Label failed workspace RPCs with error_kind
- Drop redundant explicit tonic/prost deps from xai-grok-shell
- Report real exit codes for completed background shells
- Narrow the date-rollover reminder to date-bearing templates
- Wire toolOverrides through the session and agent
- Security: Bash(git:*) allowlist matches whole command chain by prefix
- Split prompt-trigger telemetry and record classifier provenance
- Raise connectors-manager timeout to 60s
- Auto classifier honors recorded approvals for repeat actions
- Apply doctor fixes in the TUI
- Auto-mode classifier timeouts prompt instead of silently denying
- Scope subagent completion drains to the owning session
- Add the toolOverrides wire types
- Set client_identifier=grok-agent-sdk
- Accept both spellings of the workspace-teleport kill switch
- Persist one-shot occurrence journal
- Stop turns that poll the exact same tool call 16x in a row
- Copy compaction checkpoint files when forking sessions
- Auto-focus permission prompt from scrollback
- Esc cancels the running turn in non-vim and minimal modes
- List Ctrl+Z undo and redo in keyboard shortcuts
- Out-of-process macOS mic capture
- Show active auth mode on session-info
- Install the npm binary under $GROK_HOME
- Remove hover/click dead zones between dashboard items
- Route startup warnings to doctor
- Document [feedback.user] author identity config
- Extend bang command timeout
- Close combine-queued edit-hold race
- Integrate relocation recovery
- Expose privacy notice rollout flag
- Break harness discovery ref cycle so connections can idle-evict
- Shift/Alt+Enter inserts newline when editing a queued prompt
- Gate project Claude permissions on folder trust
- Echo response.create.event_id on response.created
- Toast when session creation fails from disk full
- Add shared test process lifecycle
- Enable dynamic workflows by default
- Add relocation transaction state machine
- Add shared test sandbox
- Surface auth failures on model-switch compact
- Persist durable scheduler expiry
- Confirm before removing extensions-modal items
- Re-run compact and prompt after login when compact hit expired auth
- Recap sends hosted tools under backend search
This commit is contained in:
grokkybara[bot] 2026-07-22 19:18:53 +01:00
commit a5727c5960
482 changed files with 37627 additions and 13402 deletions

View file

@ -111,7 +111,13 @@ pub fn view(snapshot: DiagnosticSnapshot<'_>) -> DiagnosticReport {
));
}
let mut findings = warnings.into_iter().filter_map(issue).collect::<Vec<_>>();
let (facts, clipboard_recovery) = facts(&snapshot, suppress_newline);
let mut findings = warnings
.into_iter()
.filter_map(finding_from_warning)
.collect::<Vec<_>>();
findings.extend(clipboard_findings(&facts, ctx, clipboard_recovery));
findings.extend(newline_finding(&facts));
findings.extend(
super::ssh_wrap_hint(
ctx.is_ssh,
@ -122,7 +128,7 @@ pub fn view(snapshot: DiagnosticSnapshot<'_>) -> DiagnosticReport {
);
DiagnosticReport {
facts: facts(&snapshot, suppress_newline),
facts,
findings,
probe_notes: probe_notes(&snapshot),
}
@ -158,7 +164,10 @@ fn runtime_xtversion(evidence: RuntimeEvidence<Option<&str>>) -> Option<&str> {
}
}
fn facts(snapshot: &DiagnosticSnapshot<'_>, suppress_newline: bool) -> DiagnosticFacts {
fn facts(
snapshot: &DiagnosticSnapshot<'_>,
suppress_newline: bool,
) -> (DiagnosticFacts, ClipboardRecovery) {
let ctx = snapshot.common.terminal;
let available_themes = match snapshot.color_level {
RuntimeEvidence::Available(color_level) => crate::theme::ThemeKind::ALL
@ -203,40 +212,83 @@ fn facts(snapshot: &DiagnosticSnapshot<'_>, suppress_newline: bool) -> Diagnosti
TmuxProbeResult::Error(_) => DataControlFact::Error,
}
};
let clipboard = clipboard_facts(snapshot, data_control);
let (clipboard, clipboard_recovery) = 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),
(
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,
},
available_themes,
total_themes: crate::theme::ThemeKind::ALL.len(),
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,
},
keyboard,
newline,
clipboard,
voice: None,
clipboard_recovery,
)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ClipboardRecovery {
Confirmed,
UnverifiedSsh,
UnverifiedContainer,
UnverifiedOther,
UnavailableSsh,
UnavailableContainer,
UnavailableLocal,
}
impl ClipboardRecovery {
fn classify(delivery: crate::clipboard::ClipboardDelivery, ssh: bool, container: bool) -> Self {
use crate::clipboard::ClipboardDelivery;
match (delivery, ssh, container) {
(ClipboardDelivery::Confirmed, _, _) => Self::Confirmed,
(ClipboardDelivery::Unverified, true, _) => Self::UnverifiedSsh,
(ClipboardDelivery::Unverified, false, true) => Self::UnverifiedContainer,
(ClipboardDelivery::Unverified, false, false) => Self::UnverifiedOther,
(ClipboardDelivery::Failed, true, _) => Self::UnavailableSsh,
(ClipboardDelivery::Failed, false, true) => Self::UnavailableContainer,
(ClipboardDelivery::Failed, false, false) => Self::UnavailableLocal,
}
}
fn legacy_fix(self) -> Option<&'static str> {
match self {
Self::Confirmed => None,
Self::UnverifiedSsh | Self::UnavailableSsh => {
Some("grok wrap <ssh command> or /minimal")
}
Self::UnverifiedContainer | Self::UnavailableContainer => {
Some("grok wrap <command> or /minimal")
}
Self::UnverifiedOther => Some("grok wrap or /minimal"),
Self::UnavailableLocal => Some("/minimal"),
}
}
}
fn clipboard_facts(
snapshot: &DiagnosticSnapshot<'_>,
data_control: DataControlFact,
) -> ClipboardFacts {
use crate::clipboard::{ClipboardDelivery, ClipboardEnvironment, expected_delivery};
) -> (ClipboardFacts, ClipboardRecovery) {
use crate::clipboard::{ClipboardEnvironment, expected_delivery};
let route = &snapshot.clipboard.route;
let environment = ClipboardEnvironment {
@ -259,46 +311,193 @@ fn clipboard_facts(
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,
let recovery = ClipboardRecovery::classify(
delivery,
fix: fix.map(str::to_owned),
}
snapshot.common.terminal.is_ssh,
snapshot.container_no_display,
);
(
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: recovery.legacy_fix().map(str::to_owned),
},
recovery,
)
}
fn issue(warning: TerminalWarning) -> Option<DiagnosticFinding> {
finding(warning, FindingDisposition::Issue)
pub(crate) fn finding_from_warning(warning: TerminalWarning) -> Option<DiagnosticFinding> {
let disposition = disposition_for(warning.category);
finding(warning, disposition)
}
pub(crate) const fn disposition_for(category: WarningCategory) -> FindingDisposition {
match category {
WarningCategory::SshWithoutWrap => FindingDisposition::Recommendation,
_ => FindingDisposition::Issue,
}
}
fn recommendation(warning: TerminalWarning) -> Option<DiagnosticFinding> {
finding(warning, FindingDisposition::Recommendation)
}
fn manual_finding(
id: DiagnosticId,
disposition: FindingDisposition,
message: impl Into<String>,
note: impl Into<String>,
) -> DiagnosticFinding {
DiagnosticFinding {
id,
disposition,
message: message.into(),
remediation: None,
automatic_remediation: None,
note: Some(note.into()),
}
}
fn clipboard_findings(
facts: &DiagnosticFacts,
ctx: &crate::terminal::TerminalContext,
recovery: ClipboardRecovery,
) -> Vec<DiagnosticFinding> {
let mut findings = Vec::new();
match recovery {
ClipboardRecovery::Confirmed => {}
ClipboardRecovery::UnverifiedSsh => findings.push(manual_finding(
crate::diagnostics::CLIPBOARD_DELIVERY_UNVERIFIED_ID,
FindingDisposition::Issue,
"Grok can't verify this clipboard route across the remote boundary",
"When you copy, Grok sends OSC 52 but can't confirm that the outer terminal accepted \
it. Each copy is also saved to a backup file; the copy message shows the path. If \
paste fails, run `grok wrap ssh <host>` on your local computer or use `/minimal`. \
For repeated SSH sessions, run `grok doctor fix ssh-wrap` on your local computer.",
)),
ClipboardRecovery::UnverifiedContainer => findings.push(manual_finding(
crate::diagnostics::CLIPBOARD_DELIVERY_UNVERIFIED_ID,
FindingDisposition::Issue,
"Grok can't verify this clipboard route across the container boundary",
"When you copy, Grok sends OSC 52 but can't confirm that the outer terminal accepted \
it. Each copy is also saved to a backup file; the copy message shows the path. If \
paste fails, start the container command with local `grok wrap <command>`, or use \
`/minimal`.",
)),
ClipboardRecovery::UnverifiedOther => findings.push(manual_finding(
crate::diagnostics::CLIPBOARD_DELIVERY_UNVERIFIED_ID,
FindingDisposition::Issue,
"Grok can't verify this clipboard route",
"Each copy is also saved to a backup file; the copy message shows the path. For a \
remote or container command, use local `grok wrap <command>`. You can also use \
`/minimal` to select text in the terminal.",
)),
ClipboardRecovery::UnavailableSsh => findings.push(manual_finding(
crate::diagnostics::CLIPBOARD_DELIVERY_UNAVAILABLE_ID,
FindingDisposition::Issue,
"This clipboard route can't reach the target clipboard",
"When you copy, Grok saves the text to the backup file shown in the copy message. To \
copy directly, run `grok wrap ssh <host>` on your local computer. For repeated SSH \
sessions, run `grok doctor fix ssh-wrap` there. You can also use `/copy <file>` or \
`/minimal`.",
)),
ClipboardRecovery::UnavailableContainer => findings.push(manual_finding(
crate::diagnostics::CLIPBOARD_DELIVERY_UNAVAILABLE_ID,
FindingDisposition::Issue,
"This clipboard route can't reach the target clipboard",
"When you copy, Grok saves the text to the backup file shown in the copy message. \
Start the container command with local `grok wrap <command>`, use `/copy <file>`, or \
use `/minimal`.",
)),
ClipboardRecovery::UnavailableLocal => findings.push(manual_finding(
crate::diagnostics::CLIPBOARD_DELIVERY_UNAVAILABLE_ID,
FindingDisposition::Issue,
"This clipboard route can't reach the target clipboard",
"When you copy, Grok saves the text to the backup file shown in the copy message. Use \
`/copy <file>` or `/minimal`, then check the native clipboard tool listed above.",
)),
}
if ctx.brand.is_vscode_family()
&& ctx.is_ssh
&& facts.clipboard.osc52_route
&& !facts.clipboard.wrap_sink
{
findings.push(manual_finding(
crate::diagnostics::VSCODE_SSH_NON_ASCII_ID,
FindingDisposition::Recommendation,
"This remote editor may change non-ASCII text copied with OSC 52",
"If pasted non-ASCII text is incorrect, use `/minimal` and select text in the \
terminal. ASCII copy and the backup file shown after the copy remain available.",
));
}
if ctx.brand == TerminalName::Iterm2
&& (ctx.is_ssh
|| facts.clipboard.native_preflight
!= crate::clipboard::NativeClipboardPreflight::LocalAvailable)
&& facts.clipboard.osc52_route
&& !facts.clipboard.wrap_sink
{
findings.push(manual_finding(
crate::diagnostics::ITERM2_CLIPBOARD_PERMISSION_ID,
FindingDisposition::Recommendation,
"iTerm2 may block OSC 52 clipboard access",
"In iTerm2, open Settings → General → Selection and turn on “Applications in \
terminal may access clipboard. Grok can't read this setting, so check it there if \
copies don't paste.",
));
}
findings
}
fn newline_finding(facts: &DiagnosticFacts) -> Option<DiagnosticFinding> {
let newline = facts.newline.as_ref()?;
let (message, note) = match newline {
NewlineFact::Vte { version } => (
"Shift+Enter can't insert a newline in this VTE terminal",
match version {
Some(version) => format!(
"Use Alt+Enter to insert a newline. This terminal reports VTE {version}. \
Upgrade to VTE 0.82 or later to use Shift+Enter."
),
None => "Use Alt+Enter to insert a newline. Upgrade to VTE 0.82 or later to use \
Shift+Enter."
.to_owned(),
},
),
NewlineFact::XtermJs { terminal } => (
"Shift+Enter can't insert a newline in this xterm.js terminal",
format!(
"Use Alt+Enter to insert a newline in {terminal}. xterm.js sends Shift+Enter as \
Enter in this setup."
),
),
NewlineFact::NoKittyKeyboardProtocol => (
"Shift+Enter can't insert a newline because the keyboard protocol is unavailable",
"Use Alt+Enter to insert a newline. If your terminal supports the Kitty keyboard \
protocol, enable it and restart Grok."
.to_owned(),
),
};
Some(manual_finding(
crate::diagnostics::NEWLINE_FALLBACK_ID,
FindingDisposition::Recommendation,
message,
note,
))
}
fn finding(warning: TerminalWarning, disposition: FindingDisposition) -> Option<DiagnosticFinding> {
let id = id_for(warning.category)?;
Some(DiagnosticFinding {
@ -327,9 +526,15 @@ pub(crate) const fn id_for(category: WarningCategory) -> Option<DiagnosticId> {
WarningCategory::WezTermKittyKeyboardOff => "wezterm-kitty",
WarningCategory::LimitedColorSupport => "limited-color",
WarningCategory::SshWithoutWrap => "ssh-wrap",
WarningCategory::NotificationProtocolFallback
| WarningCategory::FocusTrackingUnavailable
| WarningCategory::SandboxProfileConflict => return None,
WarningCategory::NotificationProtocolFallback => {
return Some(crate::diagnostics::NOTIFICATION_PROTOCOL_FALLBACK_ID);
}
WarningCategory::FocusTrackingUnavailable => {
return Some(crate::diagnostics::FOCUS_TRACKING_UNAVAILABLE_ID);
}
WarningCategory::SandboxProfileConflict => {
return Some(crate::diagnostics::SANDBOX_PROFILE_CONFLICT_ID);
}
};
Some(DiagnosticId::new("terminal", item))
}