Synced from monorepo
Synced from monorepo Changes: - Temporarily disable session share link creation in the TUI - Do not approve plan on empty Enter from the revise prompt - Expose chat product Skills via ACP available_commands_update - Return immediately from a blocking wait on an already-completed ACP task - Split headless pager module for clearer structure - Stop git worktree prune from removing user registrations on resume - Use compaction sampler tokenizer for item token counts - Opt-in extra root CAs via GROK_EXTRA_CA_BUNDLE - Cancel all session subagents when the user stops - Let the session persistence actor exit when its session ends - Make fullscreen terminal resize much cheaper on long sessions - Report honestly from kill_task when an ACP task does not exist - Hide /usage for external-auth deployments - Forward the history-load trailer’s computer_reason to the client - Remove ineffective no-op tool reminder - Declare slash-command screen-mode support in one place - Keep settings enum picker on the committed value until Enter - Reap a PTY’s full process tree - Stream tool calls from headless mode over ACP - Bridge gateway task lifecycle to ACP for chat session background tasks - Don’t warn about truncated history on a suppressed replay - Fit full-replace summarizer input and recover on context-length errors - Stop dropping agents over an unrecognized frontmatter color - Add /undo as a slash alias for /rewind - Harden sleep/wake token-refresh paths against forced re-login - Add session/list ACP method - Give each sampling backend its own conversion module - Treat an unenrolled child process as a lint error - Suppress the cancelled marker on send-now wake turns - Stop tearing down Roslyn on every edit, and read C# diagnostics Source-Revision: 2a28b4a86cfc4a4c133c35b7fc2a6a9964387c39
This commit is contained in:
parent
500129c714
commit
dd04f397b1
367 changed files with 29489 additions and 10051 deletions
|
|
@ -187,6 +187,7 @@ fn write_tmux_buffer(text: &str) -> bool {
|
|||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
xai_tty_utils::detach_std_command(&mut cmd);
|
||||
#[allow(clippy::disallowed_methods)] // short-lived clipboard helper, waited on below
|
||||
let mut child = cmd.spawn()?;
|
||||
// Bounded wait: a wedged tmux server must not freeze the UI thread.
|
||||
let status = xai_grok_shared::clipboard::wait_with_deadline(
|
||||
|
|
|
|||
|
|
@ -482,6 +482,21 @@ pub fn legacy_glyph_fallback(s: &str) -> Cow<'_, str> {
|
|||
Cow::Owned(to_legacy_glyphs(s))
|
||||
}
|
||||
|
||||
/// Single-row toast sinks: glyph fallback, then map control chars to spaces.
|
||||
/// Borrows when the input is already clean (common path).
|
||||
pub fn sanitize_toast_message(msg: &str) -> Cow<'_, str> {
|
||||
let glyph = legacy_glyph_fallback(msg);
|
||||
if !glyph.chars().any(char::is_control) {
|
||||
return glyph;
|
||||
}
|
||||
Cow::Owned(
|
||||
glyph
|
||||
.chars()
|
||||
.map(|c| if c.is_control() { ' ' } else { c })
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Pure glyph → legacy-safe mapping behind [`legacy_glyph_fallback`], split
|
||||
/// out so tests can exercise the substitution without faking the host probe.
|
||||
/// `√` matches [`check_mark`]'s fallback; `x` matches [`ballot_x`]'s.
|
||||
|
|
@ -734,6 +749,22 @@ mod tests {
|
|||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sanitize_toast_message_borrows_when_clean() {
|
||||
assert!(!is_legacy_windows_console());
|
||||
assert!(matches!(
|
||||
sanitize_toast_message("plain toast"),
|
||||
Cow::Borrowed("plain toast")
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sanitize_toast_message_maps_controls_to_spaces() {
|
||||
let out = sanitize_toast_message("a\nb\tc");
|
||||
assert_eq!(out.as_ref(), "a b c");
|
||||
assert!(!out.chars().any(char::is_control));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forced_legacy_console_override_parses_known_values() {
|
||||
assert_eq!(parse_forced_legacy_console(Some("1")), Some(true));
|
||||
|
|
|
|||
|
|
@ -45,10 +45,23 @@ pub fn browser_open_likely_available() -> bool {
|
|||
browser_open_likely_available_from_env(&env)
|
||||
}
|
||||
|
||||
/// User-facing copy when the browser opener cannot run. Includes the full
|
||||
/// URL on its own line so it is easy to select/copy in the TUI.
|
||||
const BROWSER_UNAVAILABLE_NOTICE: &str = "Could not open a browser. Open this URL manually";
|
||||
|
||||
/// Multi-line copy for agent scrollback: notice, then the full URL alone
|
||||
/// so it is easy to select/copy in the TUI.
|
||||
pub fn browser_unavailable_message(url: &str) -> String {
|
||||
format!("Could not open a browser. Open this URL manually:\n{url}")
|
||||
format!("{BROWSER_UNAVAILABLE_NOTICE}:\n{url}")
|
||||
}
|
||||
|
||||
/// Single-line welcome toast: URL first so prefix truncation keeps the
|
||||
/// destination. `copied` is true only when clipboard delivery reported
|
||||
/// success — never claim a copy that did not happen.
|
||||
pub fn browser_unavailable_line(url: &str, copied: bool) -> String {
|
||||
if copied {
|
||||
format!("{url} — {BROWSER_UNAVAILABLE_NOTICE} (URL copied)")
|
||||
} else {
|
||||
format!("{url} — {BROWSER_UNAVAILABLE_NOTICE}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Open a URL in the system's default browser/handler.
|
||||
|
|
@ -59,10 +72,12 @@ pub fn browser_unavailable_message(url: &str) -> String {
|
|||
///
|
||||
/// Returns `true` when the opener was launched (or the test seam recorded
|
||||
/// the URL). Returns `false` when the environment looks headless or spawn
|
||||
/// fails — callers should show [`browser_unavailable_message`].
|
||||
/// fails — callers should surface the URL via [`browser_unavailable_message`]
|
||||
/// (scrollback) or [`browser_unavailable_line`] (welcome toast).
|
||||
///
|
||||
/// **Callers handling untrusted input** should call [`is_safe_to_open`]
|
||||
/// first, or use [`open_url_if_safe`] / [`try_open_url`] which combine both.
|
||||
#[allow(clippy::disallowed_methods)] // fire and forget; the child is reaped when this process exits
|
||||
pub fn open_url(url: &str) -> bool {
|
||||
// Test seam: PTY e2e must observe the open without launching a real
|
||||
// browser. When set, append the URL to the file and skip the OS opener.
|
||||
|
|
@ -156,6 +171,7 @@ fn build_open_path_command(path: &std::path::Path) -> std::process::Command {
|
|||
/// expansion corrupts the percent-encoded session-directory segment in
|
||||
/// imagine media paths (e.g. `…\C%3A%5CUsers…`).
|
||||
/// - **macOS / Linux**: `open` / `xdg-open` open the file in its default app.
|
||||
#[allow(clippy::disallowed_methods)] // fire and forget; the child is reaped when this process exits
|
||||
pub fn open_path(path: &std::path::Path) -> bool {
|
||||
// Never launch a real GUI app in tests.
|
||||
#[cfg(test)]
|
||||
|
|
@ -189,6 +205,7 @@ pub fn open_path(path: &std::path::Path) -> bool {
|
|||
/// Prefer the on-disk path as-is. When the file is missing, open the parent
|
||||
/// folder (no `/select`) so the user lands near the media instead of Home.
|
||||
#[cfg(all(not(test), target_os = "windows"))]
|
||||
#[allow(clippy::disallowed_methods)] // fire and forget; the child is reaped when this process exits
|
||||
fn reveal_in_explorer(path: &std::path::Path) -> bool {
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
|
|
@ -569,11 +586,34 @@ mod tests {
|
|||
#[test]
|
||||
fn browser_unavailable_message_includes_full_url() {
|
||||
let url = "https://grok.com/supergrok?referrer=grok-build";
|
||||
let msg = browser_unavailable_message(url);
|
||||
assert!(msg.contains("Could not open a browser"));
|
||||
assert!(msg.contains(url));
|
||||
// URL on its own line for easy select/copy in the TUI.
|
||||
assert!(msg.lines().any(|l| l == url));
|
||||
assert_eq!(
|
||||
browser_unavailable_message(url),
|
||||
format!("{BROWSER_UNAVAILABLE_NOTICE}:\n{url}")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn browser_unavailable_line_is_url_first_single_line() {
|
||||
let url = "https://grok.com/supergrok?referrer=grok-build";
|
||||
let plain = browser_unavailable_line(url, false);
|
||||
assert!(plain.starts_with(url), "{plain}");
|
||||
assert!(!plain.contains('\n'), "{plain}");
|
||||
assert!(
|
||||
!plain.to_ascii_lowercase().contains("copied"),
|
||||
"must not claim copy on failure: {plain}"
|
||||
);
|
||||
assert!(
|
||||
plain.contains(BROWSER_UNAVAILABLE_NOTICE),
|
||||
"shares notice stem with multi-line form: {plain}"
|
||||
);
|
||||
|
||||
let with_copy = browser_unavailable_line(url, true);
|
||||
assert!(with_copy.starts_with(url), "{with_copy}");
|
||||
assert!(!with_copy.contains('\n'), "{with_copy}");
|
||||
assert!(
|
||||
with_copy.contains("URL copied"),
|
||||
"copy claim only when copied=true: {with_copy}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ fn run_tmux_bounded(
|
|||
timeout: Duration,
|
||||
) -> Result<TmuxCommandOutput, String> {
|
||||
let mut command = build_tmux_command(command);
|
||||
#[allow(clippy::disallowed_methods)] // bounded probe, waited on with a timeout
|
||||
let mut child = command
|
||||
.spawn()
|
||||
.map_err(|error| format!("failed to run tmux: {error}"))?;
|
||||
|
|
|
|||
Loading…
Reference in a new issue