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
|
|
@ -69,6 +69,281 @@ fn doctor_json_bypasses_unrelated_startup_state() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "spawns the real pager binary; CI/Bazel provides PAGER_BINARY"]
|
||||
fn doctor_fix_without_id_lists_tmux_fixes_from_current_probe_evidence() {
|
||||
let binary = pager_binary().expect("real pager binary is required when selected");
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let home = temp.path().join("home");
|
||||
let grok_home = temp.path().join("qhome");
|
||||
let fake_bin = temp.path().join("bin");
|
||||
std::fs::create_dir_all(&home).unwrap();
|
||||
std::fs::create_dir_all(&grok_home).unwrap();
|
||||
std::fs::create_dir_all(&fake_bin).unwrap();
|
||||
let tmux = fake_bin.join("tmux");
|
||||
std::fs::write(
|
||||
&tmux,
|
||||
"#!/bin/sh\ncase \"$*\" in\n *\"show-option -gv allow-passthrough\"*) exit 0;;\n *\"show-option -gqv extended-keys\"*) printf off;;\n *\"show-option -gqv allow-passthrough\"*) printf off;;\n *\"show-option -gqv set-clipboard\"*) printf off;;\n *\"display-message\"*) printf x;;\n *) exit 1;;\nesac\n",
|
||||
)
|
||||
.unwrap();
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
std::fs::set_permissions(&tmux, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
}
|
||||
let output = run_pager(
|
||||
&binary,
|
||||
&home,
|
||||
&grok_home,
|
||||
"/bin/bash",
|
||||
&["doctor", "fix"],
|
||||
&[
|
||||
("TMUX", "/tmp/tmux/default,1,0"),
|
||||
("PATH", fake_bin.to_str().unwrap()),
|
||||
],
|
||||
);
|
||||
assert!(output.status.success());
|
||||
let stdout = String::from_utf8(output.stdout).unwrap();
|
||||
for handle in ["tmux-clipboard", "dcs-passthrough"] {
|
||||
assert!(stdout.contains(handle), "{stdout}");
|
||||
}
|
||||
assert!(!stdout.contains("Set up local SSH wrapping"), "{stdout}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "spawns the real pager binary; CI/Bazel provides PAGER_BINARY"]
|
||||
fn doctor_tmux_fix_probes_are_bounded_and_never_write_on_timeout() {
|
||||
let binary = pager_binary().expect("real pager binary is required when selected");
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let home = temp.path().join("home");
|
||||
let grok_home = temp.path().join("qhome");
|
||||
let fake_bin = temp.path().join("bin");
|
||||
std::fs::create_dir_all(&home).unwrap();
|
||||
std::fs::create_dir_all(&grok_home).unwrap();
|
||||
std::fs::create_dir_all(&fake_bin).unwrap();
|
||||
let tmux = fake_bin.join("tmux");
|
||||
std::fs::write(&tmux, "#!/bin/sh\nsleep 30\n").unwrap();
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
std::fs::set_permissions(&tmux, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
}
|
||||
for args in [
|
||||
["doctor", "fix", "", ""],
|
||||
["doctor", "fix", "tmux-clipboard", "--yes"],
|
||||
] {
|
||||
let actual = args
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|value| !value.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
let started = std::time::Instant::now();
|
||||
let output = run_pager(
|
||||
&binary,
|
||||
&home,
|
||||
&grok_home,
|
||||
"/bin/bash",
|
||||
&actual,
|
||||
&[
|
||||
("TMUX", "/tmp/tmux/default,1,0"),
|
||||
("PATH", fake_bin.to_str().unwrap()),
|
||||
],
|
||||
);
|
||||
assert!(started.elapsed() < std::time::Duration::from_secs(12));
|
||||
if actual.len() == 2 {
|
||||
assert!(output.status.success());
|
||||
assert_eq!(output.stdout, b"No automatic fixes are available here.\n");
|
||||
} else {
|
||||
assert_eq!(output.status.code(), Some(1));
|
||||
}
|
||||
assert!(!home.join(".tmux.conf").exists());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "spawns the real pager binary; CI/Bazel provides PAGER_BINARY"]
|
||||
fn doctor_tmux_fix_kills_background_pipe_holders_after_leader_exit() {
|
||||
let binary = pager_binary().expect("real pager binary is required when selected");
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let home = temp.path().join("home");
|
||||
let grok_home = temp.path().join("qhome");
|
||||
let fake_bin = temp.path().join("bin");
|
||||
std::fs::create_dir_all(&home).unwrap();
|
||||
std::fs::create_dir_all(&grok_home).unwrap();
|
||||
std::fs::create_dir_all(&fake_bin).unwrap();
|
||||
let tmux = fake_bin.join("tmux");
|
||||
std::fs::write(&tmux, "#!/bin/sh\nsleep 30 &\nexit 0\n").unwrap();
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
std::fs::set_permissions(&tmux, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
}
|
||||
for args in [
|
||||
vec!["doctor", "fix"],
|
||||
vec!["doctor", "fix", "tmux-clipboard", "--yes"],
|
||||
] {
|
||||
let started = std::time::Instant::now();
|
||||
let output = run_pager(
|
||||
&binary,
|
||||
&home,
|
||||
&grok_home,
|
||||
"/bin/bash",
|
||||
&args,
|
||||
&[
|
||||
("TMUX", "/tmp/tmux/default,1,0"),
|
||||
("PATH", fake_bin.to_str().unwrap()),
|
||||
],
|
||||
);
|
||||
assert!(started.elapsed() < std::time::Duration::from_secs(12));
|
||||
if args.len() == 2 {
|
||||
assert!(output.status.success());
|
||||
} else {
|
||||
assert_eq!(output.status.code(), Some(1));
|
||||
}
|
||||
assert!(!home.join(".tmux.conf").exists());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
#[ignore = "spawns the real pager binary; CI/Bazel provides PAGER_BINARY"]
|
||||
fn doctor_tmux_fix_kills_term_ignoring_redirected_descendants() {
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
|
||||
let binary = pager_binary().expect("real pager binary is required when selected");
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let home = temp.path().join("home");
|
||||
let grok_home = temp.path().join("qhome");
|
||||
let fake_bin = temp.path().join("bin");
|
||||
std::fs::create_dir_all(&home).unwrap();
|
||||
std::fs::create_dir_all(&grok_home).unwrap();
|
||||
std::fs::create_dir_all(&fake_bin).unwrap();
|
||||
let tmux = fake_bin.join("tmux");
|
||||
let pid_file = temp.path().join("descendant.pid");
|
||||
std::fs::write(
|
||||
&tmux,
|
||||
format!(
|
||||
"#!/bin/sh\n( trap '' TERM; echo $$ > '{}'; exec sleep 30 ) >/dev/null 2>&1 &\nexit 0\n",
|
||||
pid_file.display()
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::set_permissions(&tmux, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
for args in [
|
||||
vec!["doctor", "fix"],
|
||||
vec!["doctor", "fix", "tmux-clipboard", "--yes"],
|
||||
] {
|
||||
let _ = std::fs::remove_file(&pid_file);
|
||||
let output = run_pager(
|
||||
&binary,
|
||||
&home,
|
||||
&grok_home,
|
||||
"/bin/bash",
|
||||
&args,
|
||||
&[
|
||||
("TMUX", "/tmp/tmux/default,1,0"),
|
||||
("PATH", fake_bin.to_str().unwrap()),
|
||||
],
|
||||
);
|
||||
assert!(output.status.success() || output.status.code() == Some(1));
|
||||
let pid: i32 = std::fs::read_to_string(&pid_file)
|
||||
.unwrap()
|
||||
.trim()
|
||||
.parse()
|
||||
.unwrap();
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
|
||||
while std::time::Instant::now() < deadline {
|
||||
// SAFETY: kill(pid, 0) only probes liveness for the positive child PID.
|
||||
if unsafe { libc::kill(pid, 0) } != 0 {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(20));
|
||||
}
|
||||
// SAFETY: same liveness probe; ESRCH is the expected result.
|
||||
assert_ne!(
|
||||
unsafe { libc::kill(pid, 0) },
|
||||
0,
|
||||
"descendant {pid} survived"
|
||||
);
|
||||
assert!(!home.join(".tmux.conf").exists());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "spawns the real pager binary; CI/Bazel provides PAGER_BINARY"]
|
||||
fn doctor_irrelevant_unsafe_byobu_does_not_break_ssh_or_plain_tmux() {
|
||||
let binary = pager_binary().expect("real pager binary is required when selected");
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let home = temp.path().join("home");
|
||||
let grok_home = temp.path().join("qhome");
|
||||
std::fs::create_dir_all(&home).unwrap();
|
||||
std::fs::create_dir_all(&grok_home).unwrap();
|
||||
|
||||
let ssh = run_pager(
|
||||
&binary,
|
||||
&home,
|
||||
&grok_home,
|
||||
"/bin/bash",
|
||||
&["doctor", "fix", "ssh-wrap", "--yes"],
|
||||
&[("BYOBU_CONFIG_DIR", "relative")],
|
||||
);
|
||||
assert!(
|
||||
ssh.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&ssh.stderr)
|
||||
);
|
||||
assert!(home.join(".bashrc").exists());
|
||||
|
||||
let plain = run_pager(
|
||||
&binary,
|
||||
&home,
|
||||
&grok_home,
|
||||
"/bin/bash",
|
||||
&["doctor", "fix"],
|
||||
&[
|
||||
("BYOBU_CONFIG_DIR", "relative"),
|
||||
("TMUX", "/tmp/tmux/default,1,0"),
|
||||
],
|
||||
);
|
||||
assert!(
|
||||
plain.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&plain.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "spawns the real pager binary; CI/Bazel provides PAGER_BINARY"]
|
||||
fn doctor_hostile_home_and_byobu_create_no_config_files() {
|
||||
let binary = pager_binary()
|
||||
.expect("real pager binary is required when selected")
|
||||
.canonicalize()
|
||||
.unwrap();
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let home = temp.path().join("home");
|
||||
let grok_home = temp.path().join("qhome");
|
||||
std::fs::create_dir_all(&home).unwrap();
|
||||
std::fs::create_dir_all(&grok_home).unwrap();
|
||||
for (key, value) in [("HOME", "."), ("BYOBU_CONFIG_DIR", "relative")] {
|
||||
let mut command = base_pager_command(&binary, &home, &grok_home, "/bin/bash");
|
||||
command
|
||||
.current_dir(temp.path())
|
||||
.env(key, value)
|
||||
.env("TMUX", "/tmp/tmux/default,1,0")
|
||||
.env("BYOBU_BACKEND", "tmux")
|
||||
.args(["doctor", "fix", "tmux-clipboard", "--yes"]);
|
||||
let output = command.output().unwrap();
|
||||
assert_eq!(
|
||||
output.status.code(),
|
||||
Some(1),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
assert!(!temp.path().join(".tmux.conf").exists());
|
||||
assert!(!temp.path().join("relative/.tmux.conf").exists());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "spawns the real pager binary; CI/Bazel provides PAGER_BINARY"]
|
||||
fn doctor_fix_without_id_lists_only_applicable_automatic_fixes() {
|
||||
|
|
@ -119,6 +394,66 @@ fn doctor_fix_without_id_lists_only_applicable_automatic_fixes() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "spawns the real pager binary; CI/Bazel provides PAGER_BINARY"]
|
||||
fn doctor_tmux_fix_yes_writes_only_actual_home_tmux_config() {
|
||||
let binary = pager_binary().expect("real pager binary is required when this test is selected");
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let home = temp.path().join("home");
|
||||
let grok_home = temp.path().join("grok-home");
|
||||
let fake_bin = temp.path().join("bin");
|
||||
std::fs::create_dir_all(&home).unwrap();
|
||||
std::fs::create_dir_all(&grok_home).unwrap();
|
||||
std::fs::create_dir_all(&fake_bin).unwrap();
|
||||
let tmux = fake_bin.join("tmux");
|
||||
std::fs::write(
|
||||
&tmux,
|
||||
"#!/bin/sh\ncase \"$*\" in\n *\"show-option -gv allow-passthrough\"*) exit 0;;\n *\"show-option -gqv allow-passthrough\"*) printf off;;\n *\"show-option -gqv set-clipboard\"*) printf off;;\n *\"display-message\"*) printf x;;\n *) exit 1;;\nesac\n",
|
||||
)
|
||||
.unwrap();
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
std::fs::set_permissions(&tmux, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
}
|
||||
|
||||
let output = run_pager(
|
||||
&binary,
|
||||
&home,
|
||||
&grok_home,
|
||||
"/bin/bash",
|
||||
&["doctor", "fix", "tmux-clipboard", "--yes"],
|
||||
&[
|
||||
("TMUX", "/tmp/tmux/default,1,0"),
|
||||
("PATH", fake_bin.to_str().unwrap()),
|
||||
],
|
||||
);
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"stdout: {}\nstderr: {}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
let stdout = String::from_utf8(output.stdout).unwrap();
|
||||
assert!(
|
||||
stdout.contains("Added `set -g set-clipboard on`"),
|
||||
"{stdout}"
|
||||
);
|
||||
assert!(
|
||||
stdout.contains("Reload tmux with `tmux source-file"),
|
||||
"{stdout}"
|
||||
);
|
||||
assert!(
|
||||
stdout.contains("Run /doctor again to verify the live setting"),
|
||||
"{stdout}"
|
||||
);
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(home.join(".tmux.conf")).unwrap(),
|
||||
"# >>> grok doctor >>>\n# >>> terminal.tmux-clipboard >>>\nset -g set-clipboard on\n# <<< terminal.tmux-clipboard <<<\n# <<< grok doctor <<<"
|
||||
);
|
||||
assert!(!grok_home.join(".tmux.conf").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "spawns the real pager binary; CI/Bazel provides PAGER_BINARY"]
|
||||
fn doctor_fix_yes_writes_only_actual_home_shell_rc() {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ const ALL_SETTINGS_EXERCISED: &[&str] = &[
|
|||
"collapsed_edit_blocks",
|
||||
"respect_manual_folds",
|
||||
"hunk_tracker_mode",
|
||||
"voice_keybind_enabled",
|
||||
"voice_capture_mode",
|
||||
"voice_stt_language",
|
||||
// Contextual-hints group + its per-tip child toggles (exercised via the
|
||||
|
|
@ -235,6 +236,12 @@ fn assert_set_bool_action(outcome: SettingsKeyOutcome, key: &str, expected: bool
|
|||
"SetRememberToolApprovals value differs from expected"
|
||||
)
|
||||
}
|
||||
("voice_keybind_enabled", Action::SetVoiceKeybindEnabled(b)) => {
|
||||
assert_eq!(
|
||||
b, expected,
|
||||
"SetVoiceKeybindEnabled value differs from expected"
|
||||
)
|
||||
}
|
||||
(
|
||||
"toolset.ask_user_question.timeout_enabled",
|
||||
Action::SetAskUserQuestionTimeoutEnabled(b),
|
||||
|
|
@ -1797,6 +1804,7 @@ fn registry_kind_membership_through_pr_14() {
|
|||
"toolset.ask_user_question.timeout_enabled",
|
||||
"auto_update",
|
||||
"show_tips",
|
||||
"voice_keybind_enabled",
|
||||
// Per-tip contextual-hint children (hidden from the top-level list,
|
||||
// toggled inside the group sub-sheet) are still Bool settings.
|
||||
"contextual_hints.undo",
|
||||
|
|
@ -1961,6 +1969,7 @@ fn defaults_round_trip_through_registry() {
|
|||
"coding_data_sharing" => SettingValue::Enum("opt-out"),
|
||||
"default_selected_permission" => SettingValue::Enum("always_allow_all_sessions"),
|
||||
"hunk_tracker_mode" => SettingValue::Enum("agent_only"),
|
||||
"voice_keybind_enabled" => SettingValue::Bool(true),
|
||||
"voice_capture_mode" => SettingValue::Enum("hold"),
|
||||
"voice_stt_language" => SettingValue::Enum("en"),
|
||||
"plan_mode" => SettingValue::Enum("off"),
|
||||
|
|
@ -2052,7 +2061,8 @@ fn settings_value_payload_matches_kind() {
|
|||
| SettingsKeyOutcome::Action(Action::SetGroupToolVerbs(_))
|
||||
| SettingsKeyOutcome::Action(Action::SetCollapsedEditBlocks(_))
|
||||
| SettingsKeyOutcome::Action(Action::SetInvertScroll(_))
|
||||
| SettingsKeyOutcome::Action(Action::SetDisplayRefreshAutoCadence(_)) => {}
|
||||
| SettingsKeyOutcome::Action(Action::SetDisplayRefreshAutoCadence(_))
|
||||
| SettingsKeyOutcome::Action(Action::SetVoiceKeybindEnabled(_)) => {}
|
||||
other => panic!(
|
||||
"expected a typed bool setter for `{}`, got {:?}",
|
||||
meta.key, other
|
||||
|
|
@ -6277,6 +6287,31 @@ fn voice_stt_language_picker_enter_dispatches_set_commit() {
|
|||
);
|
||||
}
|
||||
|
||||
/// Space-toggle on `voice_keybind_enabled` dispatches the typed setter.
|
||||
/// Default is ON (the chord works out of the box), so toggling flips it off.
|
||||
#[test]
|
||||
fn space_on_voice_keybind_enabled_dispatches_typed_setter() {
|
||||
let mut s = make_state();
|
||||
navigate_to(&mut s, "voice_keybind_enabled");
|
||||
let outcome = handle_settings_key(&mut s, &press(KeyCode::Char(' ')));
|
||||
assert_set_bool_action(outcome, "voice_keybind_enabled", false);
|
||||
}
|
||||
|
||||
/// Value-column click toggles `voice_keybind_enabled` in one click.
|
||||
#[test]
|
||||
fn mouse_click_on_voice_keybind_enabled_indicator_toggles_in_one_click() {
|
||||
let mut s = make_state();
|
||||
synth_rects(&mut s);
|
||||
let row_y = row_idx_for(&s, "voice_keybind_enabled") as u16;
|
||||
let outcome = handle_settings_mouse(
|
||||
&mut s,
|
||||
MouseEventKind::Down(crossterm::event::MouseButton::Left),
|
||||
72,
|
||||
row_y,
|
||||
);
|
||||
assert_set_bool_action(outcome, "voice_keybind_enabled", false);
|
||||
}
|
||||
|
||||
/// Value-column click on the voice_stt_language row opens the picker in ONE
|
||||
/// click (mouse ↔ keyboard parity).
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue