Synced from monorepo

Changes:
- Classify clipboard delivery confidence
- Add durable session update append
- Scope the xAI session bearer to first-party memory embedding endpoints
- Persist subagent outputs to disk and bound long-lived agent state
- Add MiniSweAgent:bash for mini-swe-agent parity
- Revert taking local sessions off the persistent shell
- Contextual tip recommending grok wrap on SSH sessions
- Voice STT bearer from model BYOK env_key/api_key
- Define exact website policies for sandbox
- Gate unsafe shell environments
- Shared pin hoist; single require_sha gate for marketplace plugins
- Server-signed is-managed claim (closes sidecar-removal downgrade)
- Optional require_sha pin for remote plugin installs
- Show session title and last exchange in the exit resume hint
- Gate shell output redirects
- Warn when fail_closed is present but not a boolean
- Add canonical text editing core (ratatui-textarea)
- Keep execution state out of goal scratch
- Add acknowledged persistence primitives
- Inherit child network restrictions in sandbox
- Fail closed when hook matchers fail to recompile
- Add MCP setup preferences for plugin MCPs
- Gate sourced shell scripts
- Gate file-typed project hooks
- grok wrap: restore terminal modes on child death
- Harden owner-only permissions on auth and MCP credentials
- Create crash dump files with owner-only permissions
- Write the agent_id cache owner-only (0600)
- SessionMetrics mode skips Mixpanel profile sync
- Dashboard: slim live-tail peek
- Yank full queued prompt text, not (+N lines)
- Defeat clock-rollback on the signed managed-config cache
- Stop early session/cancel from overtaking the prompt and wedging the turn slot
- Self-heal a diverged agent entrypoint on startup
- Add matched inference expectations in test-support
- Add AuthSingleFlight cancel/successor gap tests
- Remove consumer from external OTEL allowlist and pin scrub coverage
- Enable /copy in minimal mode
- Surface capacity and API-key detail on 429 errors
- Single-flight interactive auth
- Fix PageUp/PageDown skipping lines behind sticky prompt header
This commit is contained in:
grokkybara[bot] 2026-07-17 14:19:50 +01:00
commit 98c3b2438a
225 changed files with 18836 additions and 7156 deletions

View file

@ -6,9 +6,9 @@ pub(crate) use serde_json::json;
pub(crate) use std::path::Path;
pub(crate) use std::time::{Duration, Instant};
pub(crate) use xai_grok_pager_pty_harness::{
ContentController, MockModel, PtyHarness, ScriptedResponse, SseEvent, keys,
oauth_env_for_pager, pager_binary, seed_fake_oauth, sse, wait_for_labels_absent,
wait_for_model_via_new_sessions,
ContentController, InferenceEndpoint, InferenceRequestMatcher, MockModel, PtyHarness,
ScriptedResponse, SseEvent, keys, oauth_env_for_pager, pager_binary, seed_fake_oauth, sse,
wait_for_labels_absent, wait_for_model_via_new_sessions,
};
/// Default PTY size used by every e2e test. Large enough to render the
@ -1116,6 +1116,18 @@ const WRAP_DRAIN_TIMEOUT: Duration = Duration::from_secs(10);
/// before auth/network/sandbox.
#[cfg(unix)]
pub(crate) fn run_wrap(wrap_args: &[&str], extra_env: &[(&str, &str)]) -> (Option<u32>, String) {
run_wrap_driving(wrap_args, extra_env, |_| {})
}
/// Like [`run_wrap`], but hands the live harness to `drive` right after spawn
/// so a test can interact mid-run (wait for output, deliver signals to wrap
/// itself) before the exit-and-drain phase.
#[cfg(unix)]
pub(crate) fn run_wrap_driving(
wrap_args: &[&str],
extra_env: &[(&str, &str)],
drive: impl FnOnce(&mut PtyHarness),
) -> (Option<u32>, String) {
let binary = pager_binary().expect("resolve pager binary");
let home = tempfile::tempdir().expect("home tempdir");
let home_str = home.path().to_str().expect("utf8 home").to_owned();
@ -1128,6 +1140,8 @@ pub(crate) fn run_wrap(wrap_args: &[&str], extra_env: &[(&str, &str)]) -> (Optio
let mut harness =
PtyHarness::new(&binary, DEFAULT_ROWS, DEFAULT_COLS, &args, &env).expect("spawn grok wrap");
drive(&mut harness);
let code = harness
.wait_for_exit_and_drain(WRAP_TIMEOUT, WRAP_DRAIN_TIMEOUT)
.ok();

View file

@ -10,13 +10,18 @@ use super::common::*;
#[ignore]
async fn empty_enter_force_sends_top_queued() {
let content = ContentController::start().await.expect("start content");
// Gate turn 1's terminal event so the queue + empty-Enter provably land
// mid-turn regardless of suite load.
content.hold_agent_completions();
content.set_turns([
slow_turn_text("TURNONE"),
"TURNTWO reply to the promoted follow-up.".to_owned(),
]);
let mut turn_one = content.expect_response_blocked(
"running turn before send-now",
InferenceRequestMatcher::foreground(InferenceEndpoint::ChatCompletions),
ScriptedResponse::sse(chat_completions_message_events(&slow_turn_text("TURNONE"))),
);
let mut turn_two = content.expect_response(
"promoted queued follow-up",
InferenceRequestMatcher::foreground(InferenceEndpoint::ChatCompletions),
ScriptedResponse::sse(chat_completions_message_events(
"TURNTWO reply to the promoted follow-up.",
)),
);
let binary = pager_binary().expect("resolve pager binary");
let mut harness =
@ -32,6 +37,9 @@ async fn empty_enter_force_sends_top_queued() {
harness
.wait_for_text("TURNONE", Duration::from_secs(30))
.expect("turn 1 streaming");
tokio::time::timeout(Duration::from_secs(10), turn_one.wait_blocked())
.await
.expect("turn 1 reached the completion barrier");
harness
.inject_keys(b"please also check the logs\r")
@ -44,7 +52,7 @@ async fn empty_enter_force_sends_top_queued() {
// shell cancels turn 1 (the abort beats the held completion) and promotes
// the row to run as turn 2.
harness.inject_keys(b"\r").expect("empty Enter send-now");
content.release_agent_completions();
turn_one.release();
// The promoted row renders as a standard " " prompt block via the
// turn-start adoption (the arrow prefix distinguishes the committed block
// from the prefix-less queue row).
@ -58,6 +66,9 @@ async fn empty_enter_force_sends_top_queued() {
harness
.wait_for_text("TURNTWO", Duration::from_secs(40))
.expect("promoted turn reply");
tokio::time::timeout(Duration::from_secs(10), turn_two.wait_satisfied())
.await
.expect("promoted turn expectation satisfied");
// The send-now cancel is silent: no cancelled marker between the partial
// turn-1 output and the promoted prompt.

View file

@ -10,14 +10,25 @@ use super::common::*;
#[ignore]
async fn empty_enter_sends_top_not_last_of_two() {
let content = ContentController::start().await.expect("start content");
// Gate turn 1's terminal event so both queues + the empty Enter provably
// land while turn 1 is still the running turn.
content.hold_agent_completions();
content.set_turns([
slow_turn_text("TURNONE"),
"TURNTWO top-row send-now acknowledged.".to_owned(),
"TURNTHREE remaining queue promoted.".to_owned(),
]);
let mut turn_one = content.expect_response_blocked(
"running turn before top-row send-now",
InferenceRequestMatcher::foreground(InferenceEndpoint::ChatCompletions),
ScriptedResponse::sse(chat_completions_message_events(&slow_turn_text("TURNONE"))),
);
let mut turn_two = content.expect_response(
"top queued row",
InferenceRequestMatcher::foreground(InferenceEndpoint::ChatCompletions),
ScriptedResponse::sse(chat_completions_message_events(
"TURNTWO top-row send-now acknowledged.",
)),
);
let mut turn_three = content.expect_response(
"remaining queued row",
InferenceRequestMatcher::foreground(InferenceEndpoint::ChatCompletions),
ScriptedResponse::sse(chat_completions_message_events(
"TURNTHREE remaining queue promoted.",
)),
);
let binary = pager_binary().expect("resolve pager binary");
let mut harness =
@ -33,6 +44,9 @@ async fn empty_enter_sends_top_not_last_of_two() {
harness
.wait_for_text("TURNONE", Duration::from_secs(45))
.expect("turn 1 streaming");
tokio::time::timeout(Duration::from_secs(10), turn_one.wait_blocked())
.await
.expect("turn 1 reached completion barrier");
harness
.inject_keys(b"queue-alpha-top\r")
@ -50,7 +64,7 @@ async fn empty_enter_sends_top_not_last_of_two() {
harness
.inject_keys(b"\r")
.expect("empty Enter send-now top");
content.release_agent_completions();
turn_one.release();
// Alpha (the promoted TOP row) then bravo drain back-to-back. Each
// promoted " …" block and the intermediate TURNTWO reply is scrolled
// above the viewport by the next turn's start-adoption before a 100ms poll
@ -61,6 +75,12 @@ async fn empty_enter_sends_top_not_last_of_two() {
harness
.wait_for_text("TURNTHREE", Duration::from_secs(90))
.expect("all queued turns drained through to the final reply");
tokio::time::timeout(Duration::from_secs(10), turn_two.wait_satisfied())
.await
.expect("top queued row expectation satisfied");
tokio::time::timeout(Duration::from_secs(10), turn_three.wait_satisfied())
.await
.expect("remaining queued row expectation satisfied");
// The send-now cancel of turn 1 is silent.
assert!(

View file

@ -0,0 +1,58 @@
// Per-test-case module for the `pty_e2e` integration test crate.
#[allow(unused_imports)]
use super::common::*;
/// Dirty-death e2e: when the wrapped child dies with DEC private modes still
/// latched, `grok wrap` must emit the matching resets so the outer terminal is
/// not left broken.
///
/// The child SIGKILLs itself after enabling alt screen, all-motion mouse
/// tracking, SGR mouse encoding, bracketed paste, and cursor hide. From wrap's
/// perspective a child SIGKILL is byte-identical to the local ssh client dying
/// on a transport drop (laptop sleep, `~.`, remote host gone): the PTY hits
/// EOF with the enables' reset bytes never having arrived. Only wrap survives
/// to clean up the local terminal.
#[test]
#[ignore = "PTY e2e; run the owning pty_e2e_* Cargo test with --ignored (see Cargo.toml)"]
#[cfg(unix)]
fn wrap_child_killed_with_latched_modes_restores_terminal() {
let (code, raw) = run_wrap(
&[
"/bin/sh",
"-c",
r"printf '\033[?1049h\033[?1003h\033[?1006h\033[?2004h\033[?25l'; kill -KILL $$",
],
&[],
);
assert!(
code.is_some(),
"wrap must exit after the child is killed\nraw:\n{raw:?}"
);
// All resets must appear after the last enable (the cursor hide): they can
// only have come from wrap's own restore path, not from the dead child.
let last_enable = raw
.rfind("\x1b[?25l")
.unwrap_or_else(|| panic!("child's mode enables must pass through\nraw:\n{raw:?}"));
let reset_pos = |needle: &str| -> usize {
match raw.rfind(needle) {
Some(pos) if pos > last_enable => pos,
Some(_) => {
panic!("reset {needle:?} must appear after the child's enables\nraw:\n{raw:?}")
}
None => panic!(
"wrap must emit {needle:?} for a mode the dead child left latched\nraw:\n{raw:?}"
),
}
};
let alt_screen_leave = reset_pos("\x1b[?1049l");
for needle in ["\x1b[?1003l", "\x1b[?1006l", "\x1b[?2004l", "\x1b[?25h"] {
let pos = reset_pos(needle);
assert!(
pos < alt_screen_leave,
"alt-screen leave must come last among the resets ({needle:?} at {pos} vs \
?1049l at {alt_screen_leave})\nraw:\n{raw:?}"
);
}
}

View file

@ -0,0 +1,51 @@
// Per-test-case module for the `pty_e2e` integration test crate.
#[allow(unused_imports)]
use super::common::*;
/// Transparency e2e: when the wrapped child balances every mode it enables and
/// exits cleanly, `grok wrap` must add zero reset bytes of its own. Blindly
/// blasting resets on exit would be visible here (duplicate disables, and a
/// kitty pop that could corrupt an enclosing context's keyboard stack); the
/// mode tracker keeps clean exits byte-for-byte transparent.
#[test]
#[ignore = "PTY e2e; run the owning pty_e2e_* Cargo test with --ignored (see Cargo.toml)"]
#[cfg(unix)]
fn wrap_clean_exit_stays_byte_transparent() {
let (code, raw) = run_wrap(
&[
"/bin/sh",
"-c",
concat!(
r"printf '\033[?1049h\033[?1003h\033[?1006h\033[?2004h\033[?25l'; ",
r"printf '\033[?25h\033[?2004l\033[?1006l\033[?1003l\033[?1049l'",
),
],
&[],
);
assert_eq!(
code,
Some(0),
"clean child exit must propagate\nraw:\n{raw:?}"
);
// Exactly the child's own disables — one occurrence each. A second copy
// means wrap injected resets on a clean exit.
for needle in [
"\x1b[?1003l",
"\x1b[?1006l",
"\x1b[?2004l",
"\x1b[?25h",
"\x1b[?1049l",
] {
assert_eq!(
raw.matches(needle).count(),
1,
"clean exit must stay byte-transparent: expected exactly the child's own \
{needle:?}\nraw:\n{raw:?}"
);
}
assert!(
!raw.contains("\x1b[<u"),
"wrap must not pop a kitty keyboard stack the child never pushed\nraw:\n{raw:?}"
);
}

View file

@ -0,0 +1,48 @@
// Per-test-case module for the `pty_e2e` integration test crate.
#[allow(unused_imports)]
use super::common::*;
/// Signal-death e2e: SIGTERM delivered to `grok wrap` itself (external kill,
/// terminal-close HUP class) must not skip cleanup. Drop handlers never run on
/// signal death, so wrap needs an explicit signal path that forwards the
/// signal to the child, emits the resets for whatever the child left latched,
/// and exits with the conventional 128+N code.
///
/// Uses `run_wrap_driving` to signal wrap mid-run, and only after a READY
/// sentinel proves the enables already flowed through wrap (signaling earlier
/// would race the latch).
#[test]
#[ignore = "PTY e2e; run the owning pty_e2e_* Cargo test with --ignored (see Cargo.toml)"]
#[cfg(unix)]
fn wrap_sigterm_restores_terminal_and_exit_code() {
let (code, raw) = run_wrap_driving(
&[
"/bin/sh",
"-c",
r"printf '\033[?1003h\033[?1006h\033[?2004h\033[?25l'; printf 'WRAP_E2E_READY'; sleep 60",
],
&[],
|harness| {
harness
.wait_until(
"child mode enables to flow through wrap",
WRAP_TIMEOUT,
|h| String::from_utf8_lossy(h.raw_output()).contains("WRAP_E2E_READY"),
)
.expect("child never latched its modes");
harness.send_signal(libc::SIGTERM).expect("SIGTERM wrap");
},
);
assert_eq!(
code,
Some(143),
"SIGTERM death must surface as the conventional 128+15\nraw:\n{raw:?}"
);
for needle in ["\x1b[?1003l", "\x1b[?1006l", "\x1b[?2004l", "\x1b[?25h"] {
assert!(
raw.contains(needle),
"wrap's signal path must emit {needle:?} for the latched mode\nraw:\n{raw:?}"
);
}
}

View file

@ -30,3 +30,79 @@ mod paste_ctrl_v_image_keeps_ui_responsive_windows;
mod paste_ctrl_v_text_echoes_fast_macos;
#[path = "pty_e2e/paste_ctrl_v_text_echoes_fast_windows.rs"]
mod paste_ctrl_v_text_echoes_fast_windows;
use common::*;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore = "PTY e2e; exercises real copy output and /terminal-setup"]
async fn unknown_ssh_clipboard_delivery_is_unverified() {
let content = ContentController::start().await.expect("start content");
content.set_response(format!(
"{MOCK_RESPONSE_SENTINEL} clipboard delivery sentinel"
));
let binary = pager_binary().expect("resolve pager binary");
let mut env = content.env_for_pager();
env.push((
"SSH_CONNECTION".into(),
"scripted-test 1 127.0.0.1 2".into(),
));
let env_refs: Vec<(&str, &str)> = env
.iter()
.map(|(key, value)| (key.as_str(), value.as_str()))
.collect();
let mut harness = PtyHarness::new_in_dir(&binary, 60, 80, &[], &env_refs, Some(content.home()))
.expect("spawn pager");
harness
.wait_for_text(WELCOME_SCREEN_SENTINEL, WELCOME_TIMEOUT)
.expect("welcome");
harness
.inject_keys(format!("{PROMPT}\r").as_bytes())
.expect("submit prompt");
harness
.wait_for_text(MOCK_RESPONSE_SENTINEL, Duration::from_secs(30))
.expect("response");
harness
.wait_for_text("Worked for", Duration::from_secs(20))
.expect("turn completion marker before /copy");
inject_keys_paced(&mut harness, b"/copy 1");
harness
.wait_for_text("/copy 1", Duration::from_secs(10))
.expect("/copy command ready");
let raw_before_copy = harness.raw_output().len();
harness.inject_keys(b"\r").expect("run /copy");
let copy_deadline = Instant::now() + Duration::from_secs(10);
let payloads = loop {
harness.update(Duration::from_millis(200));
let payloads = decode_osc52_payloads(&harness.raw_output()[raw_before_copy..]);
if !payloads.is_empty() || Instant::now() >= copy_deadline {
break payloads;
}
};
assert!(
payloads
.iter()
.any(|payload| payload.contains("clipboard delivery sentinel")),
"copy must still emit the response through OSC 52: {payloads:?}"
);
harness
.wait_for_text("Copy sent", Duration::from_secs(10))
.expect("unverified copy result visible at 80 columns");
assert!(!harness.contains_text("Copy failed"));
assert!(!harness.contains_text("Copied!"));
harness
.inject_keys(b"/terminal-setup\r")
.expect("run /terminal-setup");
harness
.wait_for_text("status unverified", Duration::from_secs(10))
.expect("unverified clipboard status");
harness
.wait_for_text("grok wrap <ssh command>", Duration::from_secs(10))
.expect("wrapped SSH guidance");
assert!(!harness.contains_text("Copy failed"));
assert!(!harness.contains_text("panicked"));
harness.quit().expect("clean quit");
}

View file

@ -39,6 +39,10 @@ mod waiting_for_model_label;
mod welcome_screen;
#[path = "pty_e2e/welcome_screen_braille_logo_renders_correctly.rs"]
mod welcome_screen_braille_logo_renders_correctly;
#[path = "pty_e2e/wrap_child_killed_with_latched_modes_restores_terminal.rs"]
mod wrap_child_killed_with_latched_modes_restores_terminal;
#[path = "pty_e2e/wrap_clean_exit_stays_byte_transparent.rs"]
mod wrap_clean_exit_stays_byte_transparent;
#[path = "pty_e2e/wrap_echo_passthrough_and_exit_code.rs"]
mod wrap_echo_passthrough_and_exit_code;
#[path = "pty_e2e/wrap_explicit_path_not_found_fails_fast.rs"]
@ -47,5 +51,7 @@ mod wrap_explicit_path_not_found_fails_fast;
mod wrap_not_found_alias_routes_via_shell_contract;
#[path = "pty_e2e/wrap_osc52_sink_env_advertised_through_shell.rs"]
mod wrap_osc52_sink_env_advertised_through_shell;
#[path = "pty_e2e/wrap_sigterm_restores_terminal_and_exit_code.rs"]
mod wrap_sigterm_restores_terminal_and_exit_code;
#[path = "pty_e2e/wrap_single_string_routes_via_shell.rs"]
mod wrap_single_string_routes_via_shell;

View file

@ -73,6 +73,7 @@ const ALL_SETTINGS_EXERCISED: &[&str] = &[
"contextual_hints.send_now",
"contextual_hints.small_screen",
"contextual_hints.word_select",
"contextual_hints.ssh_wrap",
];
#[test]
@ -1602,6 +1603,7 @@ fn registry_kind_membership_through_pr_14() {
"contextual_hints.send_now",
"contextual_hints.small_screen",
"contextual_hints.word_select",
"contextual_hints.ssh_wrap",
]
.into_iter()
.collect::<std::collections::BTreeSet<_>>()
@ -1771,6 +1773,7 @@ fn defaults_round_trip_through_registry() {
"contextual_hints.send_now" => SettingValue::Bool(true),
"contextual_hints.small_screen" => SettingValue::Bool(true),
"contextual_hints.word_select" => SettingValue::Bool(true),
"contextual_hints.ssh_wrap" => SettingValue::Bool(true),
other => panic!("test must list expected default for `{other}`"),
}
};