Synced from monorepo

Synced from monorepo

Changes:
- Workspace server: surface preview-proxy metrics through the hub metric pump
- Shell: reclaim a session’s retained state in one entry
- Shell: reclaim a session’s resident state in one entry
- Pager: withhold key event types from Alacritty builds that double keys
- Tools: cancel a session’s subagents when it closes
- Pager: keep the whole plan in scrollback and separate reasoning from output in minimal mode
- Pager: probe terminal version over DA2 and include it with feedback
- SuperGrok Plus: identity, CLI, and analytics tier surfaces
- Shell: inherit the session process scope into subagents
- Pager: build @-file-search matcher lazily on first use
- Tools: fix description and output contradictions in tool definitions
- Workspace: degrade @-file-search instead of aborting on thread exhaustion
- Tools: reap a session’s LSP servers when it closes
- Tools: fix contradictions and defects in tool descriptions, schemas, and harness pools
- MCP: reap stdio MCP children on session close
- Shell: reuse spawn-time skill discovery for session telemetry
- Tools: stop leaking shell-wrapper positional params into sourced scripts (fixes activate_conda under persistent/static shell)
- Shell: self-heal corrupt session-search SQLite cache
- Workspace: cap workspace-server tokio workers on many-core hosts
- Shell: reap a session’s child processes when it closes
- Crash handler: capture SIGABRT so panic-aborts leave crash reports
- CLI chat proxy: team-scoped Grok Code managed-config admin routes
- MCP: add CLI enable/disable for MCP servers
- Shell: cap tokio worker threads for startup thread demand
- Workspace: harden git_commit and add git_sync_base operation
- Circuit breaker: add feature-gated gRPC retry policy

Source-Revision: 2a818575225183d8ca915f5632a09b8067b5156a
This commit is contained in:
grokkybara[bot] 2026-07-28 22:50:19 +00:00
commit 5da6962e4a
192 changed files with 10337 additions and 3421 deletions

View file

@ -3,7 +3,7 @@
//! Individual test modules import via `use super::common::*`.
pub(crate) use serde_json::json;
pub(crate) use std::path::Path;
pub(crate) use std::path::{Path, PathBuf};
pub(crate) use std::time::{Duration, Instant};
pub(crate) use xai_grok_pager_pty_harness::{
AgentTurnExpectation, ContentController, EnvOp, MockModel, PtyExitPoll, PtyHarness,
@ -917,6 +917,63 @@ pub(crate) const MINIMAL_IDLE_SENTINEL: &str = "minimal · /help";
pub(crate) const MINIMAL_SWITCH_BACK_IDLE_SENTINEL: &str =
"minimal · /fullscreen to go back · /help";
/// Header of minimal's parked plan-approval controls strip.
pub(crate) const PLAN_PARKED_SENTINEL: &str = "Plan ready for review";
/// A plan body the `exit_plan_mode` tool will read off disk. Every step carries
/// a unique `{tag}{NNN}` sentinel, because a truncated plan still contains its
/// head and would pass a plain substring check.
pub(crate) fn plan_body(tag: &str, lines: usize) -> String {
let mut s = format!("# {tag} Plan\n\n");
for i in 0..lines {
s.push_str(&format!("- {tag}{i:03} step of the plan\n"));
}
s
}
/// Steps of a [`plan_body`] missing from everything the user could reach by
/// scrolling: native scrollback plus the visible screen.
pub(crate) fn plan_lines_missing(harness: &mut PtyHarness, tag: &str, lines: usize) -> Vec<usize> {
let full = harness.full_text();
(0..lines)
.filter(|i| !full.contains(&format!("{tag}{i:03}")))
.collect()
}
/// Steps of a [`plan_body`] that appear more than once — the print-once guard.
pub(crate) fn plan_lines_duplicated(
harness: &mut PtyHarness,
tag: &str,
lines: usize,
) -> Vec<usize> {
let full = harness.full_text();
(0..lines)
.filter(|i| full.matches(&format!("{tag}{i:03}")).count() > 1)
.collect()
}
/// Locate `<grok_home>/sessions/<encoded cwd>/<session id>/`, where the shell
/// keeps the session's `plan.md`. Polls: the first turn creates it
/// asynchronously.
pub(crate) fn session_dir(content: &ContentController, harness: &mut PtyHarness) -> PathBuf {
let sessions = content.home().join(".grok").join("sessions");
for _ in 0..100 {
if let Ok(outer) = std::fs::read_dir(&sessions) {
for cwd_dir in outer.flatten() {
if let Ok(inner) = std::fs::read_dir(cwd_dir.path()) {
for entry in inner.flatten() {
if entry.path().is_dir() {
return entry.path();
}
}
}
}
}
harness.update(Duration::from_millis(100));
}
panic!("no session dir under {}", sessions.display());
}
/// Spawn the pager in minimal mode against `content` at the default size.
pub(crate) fn spawn_minimal(content: &ContentController) -> PtyHarness {
spawn_minimal_sized(content, DEFAULT_ROWS, DEFAULT_COLS)

View file

@ -0,0 +1,131 @@
// Per-test-case module for the `pty_e2e` integration test crate.
#[allow(unused_imports)]
use crate::common::*;
/// Taller than the terminal, so a plan held in the live region is clipped.
const PLAN_LINES: usize = 100;
fn missing(harness: &mut PtyHarness, tag: &str) -> Vec<usize> {
plan_lines_missing(harness, tag, PLAN_LINES)
}
fn duplicated(harness: &mut PtyHarness, tag: &str) -> Vec<usize> {
plan_lines_duplicated(harness, tag, PLAN_LINES)
}
fn park_plan(
content: &ContentController,
harness: &mut PtyHarness,
dir: &std::path::Path,
tag: &str,
call_id: &str,
prompt: &str,
) -> AgentTurnExpectation {
std::fs::write(dir.join("plan.md"), plan_body(tag, PLAN_LINES)).expect("seed plan.md");
let expectation = expect_tool_turn(content, call_id, "exit_plan_mode", "{}".into());
harness
.inject_keys(format!("{prompt}\r").as_bytes())
.expect("submit plan prompt");
harness
.wait_for_text(PLAN_PARKED_SENTINEL, Duration::from_secs(60))
.unwrap_or_else(|e| {
panic!(
"plan approval never parked: {e}\nscreen:\n{}",
harness.screen_contents()
)
});
for _ in 0..10 {
harness.update(Duration::from_millis(100));
}
expectation
}
/// Minimal's plan-approval contract: the plan body reaches NATIVE SCROLLBACK
/// **while the approval is parked**, not only once the user answers. Users
/// reported reading a truncated plan with nothing behind it in the scrollback;
/// design doc §6.16.
///
/// Also pins the revision path: a revised plan is a fresh `exit_plan_mode` with
/// a new `tool_call_id`, and must commit as its own block exactly once.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore]
async fn minimal_parked_plan_commits_to_scrollback() {
let content = ContentController::start().await.expect("start content");
content.set_response(format!("{MOCK_RESPONSE_SENTINEL} first turn done."));
// ~5x the screen, so live-region-only rendering is guaranteed to clip.
let mut harness = spawn_minimal_sized(&content, 20, 100);
wait_minimal_ready(&mut harness);
// A first turn, so the session (and its plan.md directory) exists.
harness.inject_keys(b"go\r").expect("submit first turn");
harness
.wait_for_full_text(MOCK_RESPONSE_SENTINEL, Duration::from_secs(40))
.expect("first turn streams");
let dir = session_dir(&content, &mut harness);
// ── plan 1, parked ──
let _first = park_plan(
&content,
&mut harness,
&dir,
"ONE",
"call_plan_one",
"present the plan",
);
assert!(
missing(&mut harness, "ONE").is_empty(),
"the whole plan must be readable while the approval is parked \
(missing {:?})\nscrollback rows: {}\nscreen:\n{}",
missing(&mut harness, "ONE"),
harness.scrollback_text().lines().count(),
harness.screen_contents(),
);
assert!(
duplicated(&mut harness, "ONE").is_empty(),
"parked plan must be printed exactly once (duplicated {:?})",
duplicated(&mut harness, "ONE"),
);
// ── revise: `s` focuses the feedback input, Enter sends it ──
harness.inject_keys(b"s").expect("request changes");
harness.update(Duration::from_millis(400));
let _second = park_plan(
&content,
&mut harness,
&dir,
"TWO",
"call_plan_two",
"make it shorter",
);
assert!(
missing(&mut harness, "TWO").is_empty(),
"the revised plan must also be readable while parked (missing {:?})",
missing(&mut harness, "TWO"),
);
assert!(
duplicated(&mut harness, "ONE").is_empty() && duplicated(&mut harness, "TWO").is_empty(),
"a revision must not re-emit either plan (ONE {:?}, TWO {:?})",
duplicated(&mut harness, "ONE"),
duplicated(&mut harness, "TWO"),
);
// ── approve ──
harness.inject_keys(b"a").expect("approve");
for _ in 0..40 {
harness.update(Duration::from_millis(100));
}
assert!(
duplicated(&mut harness, "ONE").is_empty() && duplicated(&mut harness, "TWO").is_empty(),
"approving must not re-print the plan (ONE {:?}, TWO {:?})",
duplicated(&mut harness, "ONE"),
duplicated(&mut harness, "TWO"),
);
assert!(
!harness.contains_text("panicked"),
"pager panicked\nscreen:\n{}",
harness.screen_contents()
);
quit_minimal(&mut harness);
}

View file

@ -0,0 +1,56 @@
// Per-test-case module for the `pty_e2e` integration test crate.
#[allow(unused_imports)]
use crate::common::*;
const PLAN_LINES: usize = 60;
const TAG: &str = "QUIT";
/// The permanent-loss shape of the parked-plan bug users hit: quitting without
/// answering must still leave the whole plan in the terminal. The pinned live
/// region is repainted and never retained, so only what was committed at park
/// time survives the process. Design doc §6.16.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore]
async fn minimal_parked_plan_survives_quit() {
let content = ContentController::start().await.expect("start content");
content.set_response(format!("{MOCK_RESPONSE_SENTINEL} first turn done."));
let mut harness = spawn_minimal_sized(&content, 20, 100);
wait_minimal_ready(&mut harness);
harness.inject_keys(b"go\r").expect("submit first turn");
harness
.wait_for_full_text(MOCK_RESPONSE_SENTINEL, Duration::from_secs(40))
.expect("first turn streams");
let dir = session_dir(&content, &mut harness);
std::fs::write(dir.join("plan.md"), plan_body(TAG, PLAN_LINES)).expect("seed plan.md");
let _expectation = expect_tool_turn(&content, "call_plan_quit", "exit_plan_mode", "{}".into());
harness
.inject_keys(b"present the plan\r")
.expect("submit plan prompt");
harness
.wait_for_text(PLAN_PARKED_SENTINEL, Duration::from_secs(60))
.expect("plan approval parks");
for _ in 0..10 {
harness.update(Duration::from_millis(100));
}
// Quit without answering (Ctrl+Q arms, Ctrl+Q confirms).
let _ = harness.inject_keys(b"\x11");
harness.update(Duration::from_millis(300));
let _ = harness.inject_keys(b"\x11");
for _ in 0..40 {
harness.update(Duration::from_millis(100));
}
let missing = plan_lines_missing(&mut harness, TAG, PLAN_LINES);
assert!(
missing.is_empty(),
"plan must survive in the terminal after quitting while parked \
(missing {}/{PLAN_LINES}: {missing:?})",
missing.len(),
);
let _ = harness.quit();
}

View file

@ -0,0 +1,233 @@
// Per-test-case module for the `pty_e2e` integration test crate.
#[allow(unused_imports)]
use crate::common::*;
use xai_grok_pager_pty_harness::{
InferenceEndpoint, InferenceExpectation, InferenceRequestMatcher,
};
/// Must never appear in the answer text, so the assertions can tell them apart.
const REASONING_SENTINEL: &str = "REASONINGSENTINEL";
/// `crate::glyphs::accent_bar()` on a non-legacy console.
const RAIL: &str = "\u{2503}";
/// Kept alive for the duration of the assertions.
struct Turn {
harness: PtyHarness,
_content: ContentController,
_expectation: InferenceExpectation,
}
/// Drive one minimal turn that streams reasoning and then an answer, under
/// `NO_COLOR=1` — the case that was 100% broken, because the `bg_blend` fade is
/// a complete no-op under the terminal-native palette.
async fn run_reasoning_turn(collapse_thinking: bool) -> Turn {
// Reasoning summary deltas are a Responses-API stream shape.
let content = ContentController::start_with_models(vec![
MockModel::new("test-model").with_api_backend("responses"),
])
.await
.expect("start content");
let reasoning = format!("{REASONING_SENTINEL} pondering syllables quietly and at some length");
let answer = format!("{MOCK_RESPONSE_SENTINEL} the answer body.");
let expectation = content.expect_response(
"minimal reasoning-vs-output turn",
InferenceRequestMatcher::foreground(InferenceEndpoint::Responses),
ScriptedResponse::sse(sse::responses_api_reasoning_and_text_events(
&reasoning,
&answer,
"test-model",
)),
);
content.set_response(answer.clone());
// Ingestion is gated on this toggle, and the sandbox `$HOME` has no config.
std::fs::create_dir_all(content.home().join(".grok")).expect("mk .grok");
std::fs::write(
content.home().join(".grok/config.toml"),
"[ui]\nshow_thinking_blocks = true\n",
)
.expect("write config");
if collapse_thinking {
let grok_home = content.sandbox().grok_home().to_path_buf();
std::fs::create_dir_all(&grok_home).expect("mk grok home");
std::fs::write(
grok_home.join("pager.toml"),
"[terminal]\nminimal_collapse_thinking = true\n",
)
.expect("write pager.toml");
}
let binary = pager_binary().expect("resolve pager binary");
let mut harness = PtyHarness::spawn_with_content_env(
&binary,
DEFAULT_ROWS,
DEFAULT_COLS,
&content,
MINIMAL_ARGS,
&[("NO_COLOR", "1")],
)
.expect("spawn minimal pager");
harness.set_respond_to_queries(true);
wait_minimal_ready(&mut harness);
harness
.inject_keys(format!("{PROMPT}\r").as_bytes())
.expect("submit prompt");
harness
.wait_for_full_text(MOCK_RESPONSE_SENTINEL, Duration::from_secs(30))
.expect("turn committed");
harness
.wait_for_full_text("Thought", Duration::from_secs(10))
.expect("thinking header committed");
Turn {
harness,
_content: content,
_expectation: expectation,
}
}
/// Every styled screen row carrying `needle`, rendered as
/// `["text" dim=… italic=…]` runs: the SGR attributes the terminal emulator
/// actually received, not just the glyphs.
fn styled_rows_with(harness: &PtyHarness, needle: &str) -> Vec<String> {
harness
.screen_styled()
.into_iter()
.filter(|line| line.runs.iter().any(|r| r.text.contains(needle)))
.map(|line| {
line.runs
.iter()
.map(|r| {
format!(
"[{:?} dim={} italic={}]",
r.text.trim_end(),
r.dim,
r.italic
)
})
.collect::<Vec<_>>()
.join(" ")
})
.collect()
}
/// Reasoning must read as "not the answer" in a static native scrollback, with
/// no blank-row separator, no indent, and under `NO_COLOR` no color delta at
/// all. Three orthogonal cues, on the body rows and off the assistant rows.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore]
async fn minimal_thinking_is_visually_distinct_from_output() {
let Turn { mut harness, .. } = run_reasoning_turn(false).await;
harness
.wait_for_full_text(REASONING_SENTINEL, Duration::from_secs(10))
.unwrap_or_else(|e| {
panic!(
"reasoning body must be committed: {e}\nfull:\n{}",
harness.full_text()
)
});
let screen = harness.screen_contents();
eprintln!("─── minimal screen (NO_COLOR=1) ───\n{screen}\n───");
for row in styled_rows_with(&harness, REASONING_SENTINEL) {
eprintln!("reasoning row: {row}");
}
for row in styled_rows_with(&harness, MOCK_RESPONSE_SENTINEL) {
eprintln!("answer row: {row}");
}
// 1. Structural: the rail in column 0.
let reasoning_row = screen
.lines()
.find(|l| l.contains(REASONING_SENTINEL))
.unwrap_or_else(|| panic!("reasoning row on screen:\n{screen}"));
assert!(
reasoning_row.starts_with(RAIL),
"reasoning must keep its accent rail: {reasoning_row:?}"
);
let answer_row = screen
.lines()
.find(|l| l.contains(MOCK_RESPONSE_SENTINEL))
.unwrap_or_else(|| panic!("answer row on screen:\n{screen}"));
assert!(
!answer_row.starts_with(RAIL),
"assistant output must not wear a rail: {answer_row:?}"
);
// 2. Attributes: SGR survives NO_COLOR where a foreground blend does not.
let runs_with = |needle: &str| -> Vec<_> {
harness
.screen_styled()
.into_iter()
.flat_map(|l| l.runs)
.filter(|r| r.text.contains(needle))
.collect::<Vec<_>>()
};
let reasoning_runs = runs_with(REASONING_SENTINEL);
assert!(!reasoning_runs.is_empty(), "no styled reasoning run found");
for run in &reasoning_runs {
assert!(run.dim, "reasoning must be dim under NO_COLOR: {run:?}");
assert!(run.italic, "reasoning must be italic: {run:?}");
}
let answer_runs = runs_with(MOCK_RESPONSE_SENTINEL);
assert!(!answer_runs.is_empty(), "no styled answer run found");
assert!(
answer_runs.iter().any(|r| !r.dim && !r.italic),
"assistant output must stay undimmed and upright: {answer_runs:?}"
);
assert!(
!harness.contains_text("panicked"),
"pager panicked\nscreen:\n{}",
harness.screen_contents()
);
quit_minimal(&mut harness);
}
/// The collapsed header advertises the only way back into the body, and
/// `Ctrl+E` must honour the advertisement by re-printing it in full (K10).
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore]
async fn minimal_collapse_thinking_toggle_folds_and_ctrl_e_reopens() {
let Turn { mut harness, .. } = run_reasoning_turn(true).await;
harness
.wait_for_full_text("ctrl+e to expand", Duration::from_secs(10))
.unwrap_or_else(|e| {
panic!(
"collapsed reasoning must advertise the expand key: {e}\nfull:\n{}",
harness.full_text()
)
});
eprintln!(
"─── collapsed ([terminal] minimal_collapse_thinking = true) ───\n{}\n───",
harness.screen_contents()
);
assert!(
!harness.contains_full_text(REASONING_SENTINEL),
"the body must be folded away:\n{}",
harness.full_text()
);
harness.inject_keys(b"\x05").expect("ctrl+e");
harness
.wait_for_full_text(REASONING_SENTINEL, Duration::from_secs(10))
.unwrap_or_else(|e| {
panic!(
"ctrl+e must re-print the folded reasoning: {e}\nfull:\n{}",
harness.full_text()
)
});
eprintln!("─── after ctrl+e ───\n{}\n───", harness.screen_contents());
assert!(
!harness.contains_text("panicked"),
"pager panicked\nscreen:\n{}",
harness.screen_contents()
);
quit_minimal(&mut harness);
}

View file

@ -20,6 +20,8 @@ mod minimal_flush_left_no_hpad;
mod minimal_help_opens_command_palette;
mod minimal_lookup_commits_one_line_summary;
mod minimal_new_session_keeps_history_and_resets;
mod minimal_parked_plan_commits_to_scrollback;
mod minimal_parked_plan_survives_quit;
mod minimal_queue_indicator_shows_while_running;
mod minimal_resize_preserves_committed_scrollback;
mod minimal_settings_modal_opens_and_closes;
@ -28,5 +30,6 @@ mod minimal_short_response_stays_on_screen;
mod minimal_slash_dropdown_dismisses_with_esc;
mod minimal_slash_switches_from_fullscreen;
mod minimal_slash_switches_to_fullscreen;
mod minimal_thinking_is_visually_distinct_from_output;
mod minimal_transcript_opens_in_pager;
mod minimal_transcript_pager_restore_no_artifacts;

View file

@ -5270,51 +5270,23 @@ fn default_selected_permission_mouse_click_on_indicator_opens_picker_in_one_clic
}
}
/// The `/privacy` slash command's argument parser
/// is case-insensitive and supports a deliberately-pared-down list of
/// unambiguous-semantic aliases. The unit-level coverage lives in the
/// slash command module; this e2e test pins the integration contract
/// (the parser is reachable from the slash command and produces the
/// expected `Action`).
///
/// Ambiguous aliases
/// (`on/off/true/false/enable/disable`) were DROPPED because they
/// could be read either as "turn on privacy" (=opt-out) or "turn on
/// sharing" (=opt-in). For a privacy-critical setting we err on the
/// side of explicit, unambiguous arguments. The test below verifies
/// both the accept list AND the reject list.
/// `/privacy` takes no arguments: it opens the settings page and nothing
/// else. The alias parser it used to carry (`opt-in`, `share`, `out`, …) is
/// gone — a one-word prompt alias could flip a privacy preference with none
/// of the disclosure copy in front of the user, and the ambiguous forms
/// (`on`/`off`) risked landing on the opposite of the intent.
#[test]
fn pr9_privacy_slash_command_parses_aliases() {
use xai_grok_pager::slash::commands::privacy::parse_privacy_arg;
fn pr9_privacy_slash_command_takes_no_arguments() {
use xai_grok_pager::slash::commands::builtin_commands;
use xai_grok_pager::slash::registry::CommandRegistry;
// Canonical names.
assert_eq!(parse_privacy_arg("opt-in"), Some(true));
assert_eq!(parse_privacy_arg("opt-out"), Some(false));
// Case-insensitive (sample).
assert_eq!(parse_privacy_arg("Opt-In"), Some(true));
assert_eq!(parse_privacy_arg("OPT-OUT"), Some(false));
// Unambiguous-semantic aliases (pruned list).
assert_eq!(parse_privacy_arg("in"), Some(true));
assert_eq!(parse_privacy_arg("out"), Some(false));
assert_eq!(parse_privacy_arg("share"), Some(true));
assert_eq!(parse_privacy_arg("private"), Some(false));
// Ambiguous aliases MUST be rejected. `/privacy on`
// could be read as "turn on privacy" (=opt-out, the OPPOSITE of
// what an earlier mapping returned). For a privacy
// setting, ambiguity = silent data-exfiltration risk.
for ambiguous in &["on", "off", "true", "false", "enable", "disable"] {
assert_eq!(
parse_privacy_arg(ambiguous),
None,
"ambiguous alias `{ambiguous}` MUST be rejected (PR 9 R1, Security Issue 10)",
);
}
// Unknown.
assert_eq!(parse_privacy_arg("maybe"), None);
let reg = CommandRegistry::new(builtin_commands());
let cmd = reg.get("privacy").expect("/privacy must be registered");
assert!(
!cmd.takes_args(),
"/privacy must not advertise an argument slot"
);
assert_eq!(cmd.usage(), "/privacy");
}
// ---------------------------------------------------------------------------