Synced from monorepo
Synced from monorepo Changes: - Workspace server: report `/ready` as failed with dwell on hub connect failure - Refresh OIDC token for the Grok agent in the shell - ACP terminal output recorder - Cross-platform provider auth commands in the shell - Default `/resume` to Grok sessions with a hint for hidden external sessions - Resume sessions by title with `--resume` - Limit app-builder archive size - Data-driven tag labels for slash commands - Doctor fixes for tmux - Custom provider gateways and subprocess environment policy in the shell - `/tutorial` — opt-in onboarding tour of Grok Build - Soft and required CLI version checks in the shell - Privacy banner env overrides survive live settings updates - Add remote flag to override the image-edit model - Return profile fields from auth info even when the access token is expired - Add edit control on queued prompt rows - Keep fail-closed policy when clearing orphans with no team - Setting to disable the Ctrl+Space/F8 voice shortcut - Pass `--raw` to pw-record so Linux dictation works on older PipeWire - Validate git URLs when adding marketplace entries - Stop shipping stale tool-doc parameter and tool names - Re-point dashboard attach after `/fork` only when the parent was attached - Surface Grok Computer media-generation results as file-path chunks - Clear web background-task tray on kill and keep the task description - Show privacy upsell banner in agent view until acted on - Add tools-server client callback surface - Protect persistent global hook sources Source-Revision: 95d84f443eddcbed6cbfd6eed22e2eafe6b3939d
This commit is contained in:
parent
a5727c5960
commit
69f0ba880a
286 changed files with 22939 additions and 9624 deletions
|
|
@ -12,6 +12,12 @@ pub(super) fn report() -> DiagnosticReport {
|
|||
multiplexer: MultiplexerKind::Undetected,
|
||||
byobu: None,
|
||||
ssh: false,
|
||||
tmux: crate::diagnostics::TmuxFacts {
|
||||
extended_keys: crate::diagnostics::TmuxOptionFact::Unavailable,
|
||||
set_clipboard: crate::diagnostics::TmuxOptionFact::Unavailable,
|
||||
allow_passthrough_support: crate::diagnostics::TmuxSupportFact::Unavailable,
|
||||
allow_passthrough: crate::diagnostics::TmuxOptionFact::Unavailable,
|
||||
},
|
||||
color: crate::diagnostics::ColorFacts {
|
||||
level: crate::diagnostics::RuntimeFact::Unavailable,
|
||||
available_themes: Vec::new(),
|
||||
|
|
@ -71,12 +77,7 @@ fn terminal() -> TerminalContext {
|
|||
}
|
||||
|
||||
pub(super) fn request(home: &Path, shell: &str) -> FixRequest {
|
||||
FixRequest {
|
||||
id: SSH_WRAP_ID,
|
||||
home: home.to_path_buf(),
|
||||
shell: Some(PathBuf::from(shell)),
|
||||
validator: None,
|
||||
}
|
||||
FixRequest::new_for_test(SSH_WRAP_ID, home, Some(PathBuf::from(shell)), None, None).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -101,10 +102,13 @@ fn applicable_fix_listing_uses_report_metadata_and_planner_availability() {
|
|||
let report = report();
|
||||
let local = terminal();
|
||||
let local_fixes = applicable_automatic_fixes_with(&report, &local, |id| {
|
||||
Ok(FixRequest {
|
||||
FixRequest::new_for_test(
|
||||
id,
|
||||
..request(temp.path(), "/bin/bash")
|
||||
})
|
||||
temp.path(),
|
||||
Some(PathBuf::from("/bin/bash")),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
});
|
||||
assert_eq!(
|
||||
local_fixes,
|
||||
|
|
@ -132,6 +136,557 @@ fn applicable_fix_listing_uses_report_metadata_and_planner_availability() {
|
|||
);
|
||||
}
|
||||
|
||||
fn tmux_terminal(byobu: bool) -> TerminalContext {
|
||||
TerminalContext {
|
||||
multiplexer: MultiplexerKind::Tmux,
|
||||
byobu: byobu.then_some(crate::terminal::ByobuBackend::Tmux),
|
||||
tmux_version: Some("tmux 3.4".to_owned()),
|
||||
tmux_extended_keys: Some("off".to_owned()),
|
||||
..terminal()
|
||||
}
|
||||
}
|
||||
|
||||
fn tmux_report(id: DiagnosticId, evidence: TmuxEvidence) -> DiagnosticReport {
|
||||
let mut report = report();
|
||||
report.findings.clear();
|
||||
report.facts.multiplexer = MultiplexerKind::Tmux;
|
||||
report.facts.tmux = crate::diagnostics::TmuxFacts {
|
||||
extended_keys: crate::diagnostics::TmuxOptionFact::Available(
|
||||
if evidence == TmuxEvidence::ExtendedKeys {
|
||||
"off"
|
||||
} else {
|
||||
"on"
|
||||
}
|
||||
.to_owned(),
|
||||
),
|
||||
set_clipboard: crate::diagnostics::TmuxOptionFact::Available(
|
||||
if evidence == TmuxEvidence::Clipboard {
|
||||
"off"
|
||||
} else {
|
||||
"on"
|
||||
}
|
||||
.to_owned(),
|
||||
),
|
||||
allow_passthrough_support: crate::diagnostics::TmuxSupportFact::Supported,
|
||||
allow_passthrough: crate::diagnostics::TmuxOptionFact::Available(
|
||||
if evidence == TmuxEvidence::DcsPassthrough {
|
||||
"off"
|
||||
} else {
|
||||
"on"
|
||||
}
|
||||
.to_owned(),
|
||||
),
|
||||
};
|
||||
report.findings.push(DiagnosticFinding {
|
||||
id,
|
||||
disposition: FindingDisposition::Issue,
|
||||
message: "tmux option disabled".to_owned(),
|
||||
remediation: None,
|
||||
automatic_remediation: automatic_remediation_for(id),
|
||||
note: None,
|
||||
});
|
||||
report
|
||||
}
|
||||
|
||||
fn tmux_request(home: &Path, id: DiagnosticId) -> FixRequest {
|
||||
FixRequest::new_for_test(id, home, None, None, None).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tmux_fix_registry_resolves_every_short_and_canonical_id() {
|
||||
for (id, handle, _) in automatic_fix_choices() {
|
||||
assert_eq!(resolve_fix_id(handle).unwrap(), id);
|
||||
assert_eq!(resolve_fix_id(&id.to_string()).unwrap(), id);
|
||||
assert_eq!(
|
||||
human_fix_command(id).unwrap(),
|
||||
format!("grok doctor fix {handle}")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tmux_fix_is_available_here_in_remote_sessions_while_ssh_wrap_stays_local_only() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let mut terminal = tmux_terminal(false);
|
||||
terminal.is_ssh = true;
|
||||
let mut report = tmux_report(TMUX_CLIPBOARD_ID, TmuxEvidence::Clipboard);
|
||||
report.facts.ssh = true;
|
||||
assert_eq!(
|
||||
applicable_automatic_fixes_with(&report, &terminal, |id| {
|
||||
Ok(tmux_request(temp.path(), id))
|
||||
}),
|
||||
vec![(
|
||||
TMUX_CLIPBOARD_ID,
|
||||
"tmux-clipboard",
|
||||
AutomaticFixAvailability::Here,
|
||||
)]
|
||||
);
|
||||
assert!(
|
||||
plan_fix(
|
||||
tmux_request(temp.path(), TMUX_CLIPBOARD_ID),
|
||||
&report,
|
||||
&terminal,
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tmux_specs_plan_exact_independent_managed_items() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
for (id, evidence, line) in [
|
||||
(
|
||||
TMUX_CLIPBOARD_ID,
|
||||
TmuxEvidence::Clipboard,
|
||||
"set -g set-clipboard on",
|
||||
),
|
||||
(
|
||||
DCS_PASSTHROUGH_ID,
|
||||
TmuxEvidence::DcsPassthrough,
|
||||
"set -wg allow-passthrough on",
|
||||
),
|
||||
(
|
||||
TMUX_EXTENDED_KEYS_ID,
|
||||
TmuxEvidence::ExtendedKeys,
|
||||
"set -g extended-keys on",
|
||||
),
|
||||
] {
|
||||
let plan = plan_fix(
|
||||
tmux_request(temp.path(), id),
|
||||
&tmux_report(id, evidence),
|
||||
&tmux_terminal(false),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(plan.change().requested_path, temp.path().join(".tmux.conf"));
|
||||
assert!(
|
||||
plan.change()
|
||||
.block
|
||||
.contains(&format!("# >>> {id} >>>\n{line}\n# <<< {id} <<<"))
|
||||
);
|
||||
assert!(!plan.change().block.contains("terminal.ssh-wrap"));
|
||||
let preview = format_fix_preview(&plan);
|
||||
assert!(preview.contains("does not reload or modify the live tmux server"));
|
||||
assert!(preview.contains("Run /doctor again to verify the live setting"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn safe_absolute_directory_rejects_hostile_home_and_byobu_values() {
|
||||
for value in [
|
||||
".",
|
||||
"..",
|
||||
"/",
|
||||
"relative",
|
||||
"/tmp/../escape",
|
||||
"/tmp/bad\nname",
|
||||
"~/x",
|
||||
] {
|
||||
assert!(
|
||||
matches!(
|
||||
SafeAbsoluteDirectory::parse(PathBuf::from(value), "HOME"),
|
||||
Err(FixError::UnsafeDirectory { .. })
|
||||
),
|
||||
"{value:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reload_instruction_shell_quotes_and_markdown_escapes_paths() {
|
||||
assert_eq!(
|
||||
reload_instruction(Path::new("/tmp/a b/q'v.conf")),
|
||||
"Reload tmux with `tmux source-file '/tmp/a b/q'\\''v.conf'`, or detach and reattach."
|
||||
);
|
||||
assert_eq!(
|
||||
reload_instruction(Path::new("/tmp/a`b.conf")),
|
||||
"Reload tmux with ``tmux source-file '/tmp/a`b.conf'``, or detach and reattach."
|
||||
);
|
||||
assert_eq!(
|
||||
shell_quote_path(Path::new("/tmp/a`b.conf")).unwrap(),
|
||||
"'/tmp/a`b.conf'"
|
||||
);
|
||||
assert_eq!(
|
||||
reload_instruction(Path::new("/tmp/bad\npath")),
|
||||
"Detach and reattach to activate the persistent tmux setting."
|
||||
);
|
||||
assert_eq!(markdown_code_path(Path::new("/tmp/a`b")), "``/tmp/a`b``");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn full_preview_safely_renders_backtick_requested_symlink_target_and_backup_paths() {
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let root = temp.path().canonicalize().unwrap();
|
||||
let home = root.join("home`dir");
|
||||
let target_dir = root.join("target`dir");
|
||||
std::fs::create_dir_all(&home).unwrap();
|
||||
std::fs::create_dir_all(&target_dir).unwrap();
|
||||
let target = target_dir.join("tmux`target.conf");
|
||||
std::fs::write(&target, "set -g mouse on\n").unwrap();
|
||||
symlink(&target, home.join(".tmux.conf")).unwrap();
|
||||
let plan = plan_fix(
|
||||
tmux_request(&home, TMUX_CLIPBOARD_ID),
|
||||
&tmux_report(TMUX_CLIPBOARD_ID, TmuxEvidence::Clipboard),
|
||||
&tmux_terminal(false),
|
||||
)
|
||||
.unwrap();
|
||||
let preview = format_fix_preview(&plan);
|
||||
assert!(preview.contains("File: ``"), "{preview}");
|
||||
assert!(preview.contains("Actual file: ``"), "{preview}");
|
||||
assert!(preview.contains("Backup will be saved to: ``"), "{preview}");
|
||||
assert!(preview.contains("home`dir/.tmux.conf"), "{preview}");
|
||||
assert!(preview.contains("tmux`target.conf"), "{preview}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tmux_plain_byobu_and_custom_config_paths_are_physical() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let report = tmux_report(TMUX_CLIPBOARD_ID, TmuxEvidence::Clipboard);
|
||||
let plain = plan_fix(
|
||||
tmux_request(temp.path(), TMUX_CLIPBOARD_ID),
|
||||
&report,
|
||||
&tmux_terminal(false),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
plain.change().requested_path,
|
||||
temp.path().join(".tmux.conf")
|
||||
);
|
||||
assert!(
|
||||
!plain
|
||||
.change()
|
||||
.requested_path
|
||||
.to_string_lossy()
|
||||
.contains('~')
|
||||
);
|
||||
|
||||
let custom = FixRequest::new_for_test(
|
||||
TMUX_CLIPBOARD_ID,
|
||||
temp.path(),
|
||||
None,
|
||||
None,
|
||||
Some(temp.path().join("custom-byobu")),
|
||||
)
|
||||
.unwrap();
|
||||
let byobu = plan_fix(custom, &report, &tmux_terminal(true)).unwrap();
|
||||
assert_eq!(
|
||||
byobu.change().requested_path,
|
||||
temp.path().join("custom-byobu/.tmux.conf")
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
plan_fix(
|
||||
tmux_request(temp.path(), TMUX_CLIPBOARD_ID),
|
||||
&report,
|
||||
&tmux_terminal(true)
|
||||
),
|
||||
Err(FixError::ByobuConfigUnavailable)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tmux_managed_items_coexist_and_each_apply_is_one_transaction() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let path = temp.path().join(".tmux.conf");
|
||||
for (id, evidence, line) in [
|
||||
(
|
||||
TMUX_CLIPBOARD_ID,
|
||||
TmuxEvidence::Clipboard,
|
||||
"set -g set-clipboard on",
|
||||
),
|
||||
(
|
||||
DCS_PASSTHROUGH_ID,
|
||||
TmuxEvidence::DcsPassthrough,
|
||||
"set -wg allow-passthrough on",
|
||||
),
|
||||
(
|
||||
TMUX_EXTENDED_KEYS_ID,
|
||||
TmuxEvidence::ExtendedKeys,
|
||||
"set -g extended-keys on",
|
||||
),
|
||||
] {
|
||||
let plan = plan_fix(
|
||||
tmux_request(temp.path(), id),
|
||||
&tmux_report(id, evidence),
|
||||
&tmux_terminal(false),
|
||||
)
|
||||
.unwrap();
|
||||
let outcome = apply_fix(plan).unwrap();
|
||||
assert_eq!(outcome.activation(), FixActivation::RequiresReload);
|
||||
assert_eq!(outcome.changed_path(), path);
|
||||
assert!(format_fix_success(&outcome).contains("Run /doctor again"));
|
||||
assert!(std::fs::read_to_string(&path).unwrap().contains(line));
|
||||
}
|
||||
let content = std::fs::read_to_string(&path).unwrap();
|
||||
assert_eq!(content.matches("# >>> grok doctor >>>").count(), 1);
|
||||
for id in [TMUX_CLIPBOARD_ID, DCS_PASSTHROUGH_ID, TMUX_EXTENDED_KEYS_ID] {
|
||||
assert_eq!(content.matches(&format!("# >>> {id} >>>")).count(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tmux_scanner_handles_server_scopes_separators_prefixes_and_native_blocks() {
|
||||
let path = Path::new("/tmp/tmux.conf");
|
||||
for spec in [&TMUX_CLIPBOARD_SPEC, &TMUX_EXTENDED_KEYS_SPEC] {
|
||||
let healthy = spec.healthy_values[0];
|
||||
for assignment in [
|
||||
format!("set {} {healthy}\n", spec.option),
|
||||
format!("set -s {} {healthy}\n", spec.option),
|
||||
format!("set-option -gq {} {healthy}\n", spec.option),
|
||||
format!("set -w {} {healthy}\n", spec.option),
|
||||
format!("FOO=bar set -g {} {healthy}\n", spec.option),
|
||||
format!("set -g mouse on; set -g {} {healthy}\n", spec.option),
|
||||
] {
|
||||
assert_eq!(
|
||||
scan_direct_tmux_option(&assignment, path, spec).unwrap(),
|
||||
DirectOptionState::Healthy,
|
||||
"{assignment:?}"
|
||||
);
|
||||
}
|
||||
for conflict in [
|
||||
format!("set {} off\n", spec.option),
|
||||
format!("set -s {} off\n", spec.option),
|
||||
format!("set-option -g {} off\n", spec.option),
|
||||
format!("set -w {} off\n", spec.option),
|
||||
format!("set -g mouse on; set -g {} off\n", spec.option),
|
||||
format!("set -g {} o\\\nff\n", spec.option),
|
||||
] {
|
||||
assert!(
|
||||
matches!(
|
||||
scan_direct_tmux_option(&conflict, path, spec),
|
||||
Err(FixError::ExistingCustomization { .. })
|
||||
),
|
||||
"{conflict:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let spec = &DCS_PASSTHROUGH_SPEC;
|
||||
for healthy in [
|
||||
"setw -g allow-passthrough on\n",
|
||||
"set-window-option -g allow-passthrough all\n",
|
||||
"set -wg allow-passthrough on\n",
|
||||
] {
|
||||
assert_eq!(
|
||||
scan_direct_tmux_option(healthy, path, spec).unwrap(),
|
||||
DirectOptionState::Healthy,
|
||||
"{healthy:?}"
|
||||
);
|
||||
}
|
||||
for conflict in [
|
||||
"setw -g allow-passthrough off\n",
|
||||
"set-window-option -g allow-passthrough off\n",
|
||||
"set -wg allow-passthrough off\n",
|
||||
] {
|
||||
assert!(
|
||||
matches!(
|
||||
scan_direct_tmux_option(conflict, path, spec),
|
||||
Err(FixError::ExistingCustomization { .. })
|
||||
),
|
||||
"{conflict:?}"
|
||||
);
|
||||
}
|
||||
for local in [
|
||||
"set allow-passthrough on\n",
|
||||
"setw allow-passthrough on\n",
|
||||
"setw -t:1 allow-passthrough off\n",
|
||||
] {
|
||||
assert_eq!(
|
||||
scan_direct_tmux_option(local, path, spec).unwrap(),
|
||||
DirectOptionState::Absent,
|
||||
"{local:?}"
|
||||
);
|
||||
}
|
||||
|
||||
for spec in [
|
||||
&TMUX_CLIPBOARD_SPEC,
|
||||
&DCS_PASSTHROUGH_SPEC,
|
||||
&TMUX_EXTENDED_KEYS_SPEC,
|
||||
] {
|
||||
for ignored in [
|
||||
format!("# set -g {} off\n", spec.option),
|
||||
format!("set -g @{} off\n", spec.option),
|
||||
format!("set -g {}-copy off\n", spec.option),
|
||||
format!("%if 1\nset -g {} off\n%endif\n", spec.option),
|
||||
format!("if-shell true {{ set -g {} off }}\n", spec.option),
|
||||
] {
|
||||
assert_eq!(
|
||||
scan_direct_tmux_option(&ignored, path, spec).unwrap(),
|
||||
DirectOptionState::Absent,
|
||||
"{ignored:?}"
|
||||
);
|
||||
}
|
||||
for ambiguous in [
|
||||
format!("se -g {} off\n", spec.option),
|
||||
format!("set -g {} off extra\n", spec.option),
|
||||
format!("set -g {}\n", spec.option),
|
||||
format!("set -g {} 'unterminated\n", spec.option),
|
||||
format!("set -g {} \\\n", spec.option),
|
||||
format!("set -t target\nset -g {} off\n", spec.option),
|
||||
] {
|
||||
assert!(
|
||||
matches!(
|
||||
scan_direct_tmux_option(&ambiguous, path, spec),
|
||||
Err(FixError::ExistingCustomization { .. })
|
||||
),
|
||||
"{ambiguous:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conflicting_direct_form_after_managed_block_fails_persistent_verification() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let path = temp.path().join(".tmux.conf");
|
||||
for conflict in [
|
||||
"set set-clipboard off",
|
||||
"set -s set-clipboard off",
|
||||
"set-option -g set-clipboard off",
|
||||
"set -g mouse on; set -g set-clipboard off",
|
||||
"se -g set-clipboard off",
|
||||
] {
|
||||
std::fs::write(
|
||||
&path,
|
||||
format!(
|
||||
"# >>> grok doctor >>>\n# >>> terminal.tmux-clipboard >>>\nset -g set-clipboard on\n# <<< terminal.tmux-clipboard <<<\n# <<< grok doctor <<<\n{conflict}\n"
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
!tmux_option_configured(&path, &TMUX_CLIPBOARD_SPEC),
|
||||
"{conflict}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn healthy_direct_does_not_suppress_repair_of_noncanonical_managed_item() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let path = temp.path().join(".tmux.conf");
|
||||
let report = tmux_report(TMUX_CLIPBOARD_ID, TmuxEvidence::Clipboard);
|
||||
for content in [
|
||||
"set -g set-clipboard on\n# >>> grok doctor >>>\n# >>> terminal.tmux-clipboard >>>\nset -g set-clipboard off\n# <<< terminal.tmux-clipboard <<<\n# <<< grok doctor <<<\n",
|
||||
"# >>> grok doctor >>>\n# >>> terminal.tmux-clipboard >>>\nset -g set-clipboard off\n# <<< terminal.tmux-clipboard <<<\n# <<< grok doctor <<<\nset -g set-clipboard on\n",
|
||||
] {
|
||||
std::fs::write(&path, content).unwrap();
|
||||
let plan = plan_fix(
|
||||
tmux_request(temp.path(), TMUX_CLIPBOARD_ID),
|
||||
&report,
|
||||
&tmux_terminal(false),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(format_fix_preview(&plan).contains("Text to add:\n"));
|
||||
let outcome = apply_fix(plan).unwrap();
|
||||
assert_eq!(outcome.status(), FixStatus::Applied);
|
||||
assert!(
|
||||
std::fs::read_to_string(&path)
|
||||
.unwrap()
|
||||
.contains("# >>> terminal.tmux-clipboard >>>\nset -g set-clipboard on\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tmux_applicability_uses_exact_positive_probe_gates() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let terminal = tmux_terminal(false);
|
||||
let mut clipboard = tmux_report(TMUX_CLIPBOARD_ID, TmuxEvidence::Clipboard);
|
||||
clipboard.facts.tmux.set_clipboard =
|
||||
crate::diagnostics::TmuxOptionFact::Available("external".to_owned());
|
||||
assert!(matches!(
|
||||
plan_fix(
|
||||
tmux_request(temp.path(), TMUX_CLIPBOARD_ID),
|
||||
&clipboard,
|
||||
&terminal
|
||||
),
|
||||
Err(FixError::TmuxNotApplicable)
|
||||
));
|
||||
|
||||
let mut dcs = tmux_report(DCS_PASSTHROUGH_ID, TmuxEvidence::DcsPassthrough);
|
||||
for support in [
|
||||
crate::diagnostics::TmuxSupportFact::Unsupported,
|
||||
crate::diagnostics::TmuxSupportFact::Unavailable,
|
||||
crate::diagnostics::TmuxSupportFact::Error,
|
||||
] {
|
||||
dcs.facts.tmux.allow_passthrough_support = support;
|
||||
assert!(matches!(
|
||||
plan_fix(
|
||||
tmux_request(temp.path(), DCS_PASSTHROUGH_ID),
|
||||
&dcs,
|
||||
&terminal
|
||||
),
|
||||
Err(FixError::TmuxNotApplicable)
|
||||
));
|
||||
}
|
||||
|
||||
let mut extended = tmux_report(TMUX_EXTENDED_KEYS_ID, TmuxEvidence::ExtendedKeys);
|
||||
extended.facts.tmux.extended_keys = crate::diagnostics::TmuxOptionFact::Unavailable;
|
||||
assert!(matches!(
|
||||
plan_fix(
|
||||
tmux_request(temp.path(), TMUX_EXTENDED_KEYS_ID),
|
||||
&extended,
|
||||
&terminal
|
||||
),
|
||||
Err(FixError::TmuxNotApplicable)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tmux_stale_plan_and_idempotence_reuse_managed_writer_safety() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let path = temp.path().join(".tmux.conf");
|
||||
std::fs::write(&path, "set -g mouse on\n").unwrap();
|
||||
let report = tmux_report(TMUX_CLIPBOARD_ID, TmuxEvidence::Clipboard);
|
||||
let plan = plan_fix(
|
||||
tmux_request(temp.path(), TMUX_CLIPBOARD_ID),
|
||||
&report,
|
||||
&tmux_terminal(false),
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(&path, "set -g mouse off\n").unwrap();
|
||||
assert!(matches!(
|
||||
apply_fix(plan),
|
||||
Err(FixError::TmuxManaged(
|
||||
xai_grok_config::managed_text::ManagedConfigError::StalePlan(_)
|
||||
))
|
||||
));
|
||||
|
||||
std::fs::write(&path, "set -g set-clipboard on\n").unwrap();
|
||||
let plan = plan_fix(
|
||||
tmux_request(temp.path(), TMUX_CLIPBOARD_ID),
|
||||
&report,
|
||||
&tmux_terminal(false),
|
||||
)
|
||||
.unwrap();
|
||||
let preview = format_fix_preview(&plan);
|
||||
assert!(preview.contains("Text to add: None"), "{preview}");
|
||||
assert!(!preview.contains("Backup will be saved"), "{preview}");
|
||||
let outcome = apply_fix(plan).unwrap();
|
||||
assert_eq!(outcome.status(), FixStatus::AlreadyConfigured);
|
||||
assert!(verify_persistent_fix(&outcome));
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(&path).unwrap(),
|
||||
"set -g set-clipboard on\n"
|
||||
);
|
||||
|
||||
let stale = plan_fix(
|
||||
tmux_request(temp.path(), TMUX_CLIPBOARD_ID),
|
||||
&report,
|
||||
&tmux_terminal(false),
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(&path, "set -g set-clipboard off\n").unwrap();
|
||||
assert!(matches!(
|
||||
apply_fix(stale),
|
||||
Err(FixError::TmuxManaged(
|
||||
xai_grok_config::managed_text::ManagedConfigError::StalePlan(_)
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bash_zsh_and_fish_plans_use_exact_paths_and_aliases() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
|
|
@ -145,22 +700,26 @@ fn bash_zsh_and_fish_plans_use_exact_paths_and_aliases() {
|
|||
),
|
||||
] {
|
||||
let plan = plan_fix(request(temp.path(), shell), &report(), &terminal()).unwrap();
|
||||
assert_eq!(plan.id, SSH_WRAP_ID);
|
||||
assert_eq!(plan.changes[0].requested_path, temp.path().join(relative));
|
||||
assert_eq!(plan.id(), SSH_WRAP_ID);
|
||||
assert_eq!(plan.change().requested_path, temp.path().join(relative));
|
||||
assert_eq!(
|
||||
plan.changes[0].block,
|
||||
plan.change().block,
|
||||
format!(
|
||||
"# >>> grok doctor >>>\n# >>> terminal.ssh-wrap >>>\n{alias}\n# <<< terminal.ssh-wrap <<<\n# <<< grok doctor <<<"
|
||||
)
|
||||
);
|
||||
assert!(plan.caveats.iter().any(|line| line.contains("command ssh")));
|
||||
assert!(plan.caveats.iter().any(|line| line.contains("ssh -f")));
|
||||
assert!(
|
||||
plan.caveats
|
||||
plan.caveats()
|
||||
.iter()
|
||||
.any(|line| line.contains("command ssh"))
|
||||
);
|
||||
assert!(plan.caveats().iter().any(|line| line.contains("ssh -f")));
|
||||
assert!(
|
||||
plan.caveats()
|
||||
.iter()
|
||||
.any(|line| line.contains("ControlPersist"))
|
||||
);
|
||||
assert!(plan.caveats.iter().any(|line| line.contains("~^Z")));
|
||||
assert!(plan.caveats().iter().any(|line| line.contains("~^Z")));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -367,8 +926,8 @@ fn comments_and_managed_alias_do_not_create_false_conflicts() {
|
|||
.unwrap();
|
||||
let plan = plan_fix(request(temp.path(), "/bin/zsh"), &report(), &terminal()).unwrap();
|
||||
let outcome = apply_fix(plan).unwrap();
|
||||
assert_eq!(outcome.status, FixStatus::AlreadyConfigured);
|
||||
assert!(outcome.backup_path.is_none());
|
||||
assert_eq!(outcome.status(), FixStatus::AlreadyConfigured);
|
||||
assert!(outcome.backup_path().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -409,9 +968,42 @@ fn stale_plan_is_rejected_and_apply_verifies_postcondition() {
|
|||
|
||||
let plan = plan_fix(request(temp.path(), "/bin/bash"), &report(), &terminal()).unwrap();
|
||||
let outcome = apply_fix(plan).unwrap();
|
||||
assert_eq!(outcome.status, FixStatus::Applied);
|
||||
assert_eq!(outcome.id, SSH_WRAP_ID);
|
||||
assert_eq!(outcome.status(), FixStatus::Applied);
|
||||
assert_eq!(outcome.id(), SSH_WRAP_ID);
|
||||
assert_eq!(outcome.shell(), Some(ShellKind::Bash));
|
||||
assert!(managed_alias_configured(&path, ShellKind::Bash));
|
||||
assert!(outcome.managed_alias_is_configured());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ssh_wrap_outcome_verifies_with_planned_shell_not_process_shell() {
|
||||
// Post-apply verification must use the shell stored on the outcome. Even if
|
||||
// `$SHELL` is missing or points at a different shell family, a successful
|
||||
// apply against bash must still report the managed alias as configured.
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let path = temp.path().join(".bashrc");
|
||||
let plan = plan_fix(request(temp.path(), "/bin/bash"), &report(), &terminal()).unwrap();
|
||||
let outcome = apply_fix(plan).unwrap();
|
||||
assert_eq!(outcome.shell(), Some(ShellKind::Bash));
|
||||
assert_eq!(outcome.changed_path(), path);
|
||||
assert!(outcome.managed_alias_is_configured());
|
||||
|
||||
// Fish uses a different alias syntax; checking the bash-written path with
|
||||
// fish must not count as configured. The outcome keeps bash regardless.
|
||||
assert!(!managed_alias_configured(&path, ShellKind::Fish));
|
||||
assert!(
|
||||
outcome.managed_alias_is_configured(),
|
||||
"outcome must keep the planned bash shell, not re-derive from $SHELL"
|
||||
);
|
||||
|
||||
let filtered = configured_report(report(), outcome.managed_alias_is_configured());
|
||||
assert!(
|
||||
!filtered
|
||||
.findings
|
||||
.iter()
|
||||
.any(|finding| finding.id == SSH_WRAP_ID),
|
||||
"configured_report must drop ssh-wrap when outcome shell matches the write"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -437,7 +1029,8 @@ fn configured_report_reaches_pass_state_only_for_exact_managed_alias() {
|
|||
healthy.findings.clear();
|
||||
let plan = plan_fix(request(temp.path(), "/bin/bash"), &healthy, &terminal()).unwrap();
|
||||
assert_eq!(
|
||||
plan.id, SSH_WRAP_ID,
|
||||
plan.id(),
|
||||
SSH_WRAP_ID,
|
||||
"healthy reports can plan idempotent setup"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue