Synced from monorepo
Changes: - Gate session-lifecycle heap steady state with a dhat soak - Unbreak merge lifecycle e2e after default model → grok-4.5 - Scan home-scope rules dirs at <root>/rules - Complete text-input paste and terminal parity - Gate project roles and personas - Use canonical editing in dialogs - Use canonical editing in search bars - Reject ambiguous MCP tool IDs - Harden Git operands for plugins - Simplify queue drain API - Pass RFC 9207 iss through MCP OAuth token exchange - Show leader roster when local agents map is empty - Use canonical editing in Persona views - Remove marketplace default-skills auto-install and purge old installs - Use canonical editing in extension forms - Add canonical dashboard text editing - Use canonical editing in settings - Add /summarize as a /recap alias - Restore previous agent when exiting dashboard - Use tool_choice auto for compaction - Settings toggle for snap-prompt-to-top on send - Update default models to grok-4.5 - Source login shell once for local bash (env + alias/function snapshot) - Template hardcoded param names in server-native tool descriptions - Fix System-Reminder XML tag injection in CLAUDE.md via agents_md - Fix remote workspace-server hardcoding LSP trust (repo code execution risk) - Clear orphaned tool-call updates at turn end - Suppress task wake after cancel - Send x-grok-client-identifier on direct API tool calls - Harden dashboard peek lease transitions - Host /btw side panel in live region (minimal mode) - Bound scroll presentation latency - Highlight multi-line constructs correctly in diffs and the file viewer - Block web_fetch non-public IPs; local opt-in is explicit-host only - Seed coding_data_retention_opt_out=false for OAuth e2es in pty-harness - Follow up clipboard delivery feedback - Use canonical editing in pickers - Route TextArea through canonical editor - Persistent "watching" status row; quieter turn markers - Gate sensitive edit targets - Expose agent registry counts and gate session churn on them - Default coding data sharing to opt-out until server preference applies - Wire chat attachment ids through gateway prompts - On auth refresh failure, issue retry - Forward preview provenance and computer lifecycle state - Document independent privacy controls and scope /privacy output - Strip SamplingError Display prefix on rate-limit UI copy - Stop dumping Cloudflare HTML into Retry failed - Disable in-place prompt edit (scroll jank on enter) - Strip forced ANSI color from gh pr view JSON - Plumb bash tool description onto ToolUsageCard wire
This commit is contained in:
parent
98c3b2438a
commit
7cfcb20d2b
292 changed files with 23315 additions and 9209 deletions
|
|
@ -1,6 +1,38 @@
|
|||
/// Default auto-compact threshold (% of context window) when no source sets it.
|
||||
pub const DEFAULT_AUTO_COMPACT_THRESHOLD_PERCENT: u8 = 85;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum CompactionToolChoice {
|
||||
#[default]
|
||||
Auto,
|
||||
None,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for CompactionToolChoice {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.trim().to_ascii_lowercase().as_str() {
|
||||
"auto" => Ok(Self::Auto),
|
||||
"none" => Ok(Self::None),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const ENV_COMPACTION_TOOL_CHOICE: &str = "GROK_COMPACTION_TOOL_CHOICE";
|
||||
|
||||
pub fn resolve_compaction_tool_choice_from(
|
||||
env: Option<&str>,
|
||||
config: Option<&str>,
|
||||
remote: Option<&str>,
|
||||
) -> CompactionToolChoice {
|
||||
env.and_then(|s| s.parse().ok())
|
||||
.or_else(|| config.and_then(|s| s.parse().ok()))
|
||||
.or_else(|| remote.and_then(|s| s.parse().ok()))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Env-var override for `auto_compact_threshold_percent`. Parsed as `u8`;
|
||||
/// out-of-range or unparseable values are ignored.
|
||||
pub(crate) const ENV_AUTO_COMPACT_THRESHOLD_PERCENT: &str = "GROK_AUTO_COMPACT_THRESHOLD_PERCENT";
|
||||
|
|
@ -140,3 +172,48 @@ mod compaction_wall_clock_budget_tests {
|
|||
assert_eq!(resolve(Some(5)), 5); // low values pass through (warned, not clamped)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod compaction_tool_choice_tests {
|
||||
use super::{CompactionToolChoice, resolve_compaction_tool_choice_from as resolve};
|
||||
|
||||
#[test]
|
||||
fn default_is_auto() {
|
||||
assert_eq!(resolve(None, None, None), CompactionToolChoice::Auto);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn precedence_env_over_config_over_remote() {
|
||||
assert_eq!(
|
||||
resolve(Some("none"), Some("auto"), Some("auto")),
|
||||
CompactionToolChoice::None
|
||||
);
|
||||
assert_eq!(
|
||||
resolve(None, Some("none"), Some("auto")),
|
||||
CompactionToolChoice::None
|
||||
);
|
||||
assert_eq!(
|
||||
resolve(None, None, Some("none")),
|
||||
CompactionToolChoice::None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn garbage_falls_through() {
|
||||
assert_eq!(
|
||||
resolve(Some("garbage"), None, Some("none")),
|
||||
CompactionToolChoice::None
|
||||
);
|
||||
assert_eq!(
|
||||
resolve(Some("garbage"), Some("also-bad"), None),
|
||||
CompactionToolChoice::Auto
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str_case_insensitive() {
|
||||
assert_eq!("AUTO".parse(), Ok(CompactionToolChoice::Auto));
|
||||
assert_eq!(" None ".parse(), Ok(CompactionToolChoice::None));
|
||||
assert!("required".parse::<CompactionToolChoice>().is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,137 @@ fn resolve_search_tool_enabled(
|
|||
env.or(config).or(managed).unwrap_or(true)
|
||||
}
|
||||
|
||||
const ENV_LOGIN_SHELL_CAPTURE: &str = "GROK_LOGIN_ENV";
|
||||
|
||||
fn login_shell_capture_from_toml(v: Option<&TomlValue>) -> Option<bool> {
|
||||
v?.get("toolset")?
|
||||
.get("bash")?
|
||||
.get("login_shell_capture")?
|
||||
.as_bool()
|
||||
}
|
||||
|
||||
pub fn resolve_login_shell_capture(remote: Option<bool>) -> bool {
|
||||
let requirements = crate::config::load_merged_requirements();
|
||||
let layers = match crate::config::ConfigLayers::load() {
|
||||
Ok(l) => Some(l),
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %e, "login_shell_capture: failed to load config layers");
|
||||
None
|
||||
}
|
||||
};
|
||||
resolve_login_shell_capture_tiers(
|
||||
requirements.as_ref(),
|
||||
layers.as_ref().map(|l| &l.user),
|
||||
layers.as_ref().map(|l| &l.managed),
|
||||
layers.as_ref().map(|l| &l.system_managed),
|
||||
remote,
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_login_shell_capture_tiers(
|
||||
requirements: Option<&TomlValue>,
|
||||
user: Option<&TomlValue>,
|
||||
managed: Option<&TomlValue>,
|
||||
system_managed: Option<&TomlValue>,
|
||||
remote: Option<bool>,
|
||||
) -> bool {
|
||||
use crate::agent::config::BoolFlag;
|
||||
BoolFlag::env(ENV_LOGIN_SHELL_CAPTURE)
|
||||
.requirement(login_shell_capture_from_toml(requirements))
|
||||
.config(login_shell_capture_from_toml(user))
|
||||
.managed(
|
||||
login_shell_capture_from_toml(managed)
|
||||
.or_else(|| login_shell_capture_from_toml(system_managed)),
|
||||
)
|
||||
.feature_flag(remote)
|
||||
.default(true)
|
||||
.resolve()
|
||||
.value
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod login_shell_capture_tests {
|
||||
use super::{ENV_LOGIN_SHELL_CAPTURE, resolve_login_shell_capture_tiers};
|
||||
use toml::Value as TomlValue;
|
||||
|
||||
static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
fn guard() -> std::sync::MutexGuard<'static, ()> {
|
||||
let g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
||||
unsafe { std::env::remove_var(ENV_LOGIN_SHELL_CAPTURE) };
|
||||
g
|
||||
}
|
||||
|
||||
fn cfg(enabled: bool) -> TomlValue {
|
||||
toml::from_str(&format!(
|
||||
"[toolset.bash]\nlogin_shell_capture = {enabled}\n"
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn defaults_on() {
|
||||
let _g = guard();
|
||||
assert!(resolve_login_shell_capture_tiers(
|
||||
None, None, None, None, None
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_flag_can_disable() {
|
||||
let _g = guard();
|
||||
assert!(!resolve_login_shell_capture_tiers(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(false)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_config_beats_remote() {
|
||||
let _g = guard();
|
||||
assert!(resolve_login_shell_capture_tiers(
|
||||
None,
|
||||
Some(&cfg(true)),
|
||||
None,
|
||||
None,
|
||||
Some(false)
|
||||
));
|
||||
assert!(!resolve_login_shell_capture_tiers(
|
||||
None,
|
||||
Some(&cfg(false)),
|
||||
None,
|
||||
None,
|
||||
Some(true)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn env_beats_config_and_remote() {
|
||||
let _g = guard();
|
||||
unsafe { std::env::set_var(ENV_LOGIN_SHELL_CAPTURE, "0") };
|
||||
let off = resolve_login_shell_capture_tiers(None, Some(&cfg(true)), None, None, Some(true));
|
||||
unsafe { std::env::remove_var(ENV_LOGIN_SHELL_CAPTURE) };
|
||||
assert!(!off);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requirements_win_outright() {
|
||||
let _g = guard();
|
||||
unsafe { std::env::set_var(ENV_LOGIN_SHELL_CAPTURE, "1") };
|
||||
let off = resolve_login_shell_capture_tiers(
|
||||
Some(&cfg(false)),
|
||||
Some(&cfg(true)),
|
||||
None,
|
||||
None,
|
||||
Some(true),
|
||||
);
|
||||
unsafe { std::env::remove_var(ENV_LOGIN_SHELL_CAPTURE) };
|
||||
assert!(!off);
|
||||
}
|
||||
}
|
||||
|
||||
/// Env override for `[toolset.ask_user_question] timeout_enabled` (parsed by
|
||||
/// the shared [`xai_grok_config::env_bool`] via `BoolFlag`). The secs env var
|
||||
/// lives in the tools crate (`RESPONSE_TIMEOUT_ENV`), parsed once there.
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ pub async fn set_show_timeline(value: bool) -> Result<()> {
|
|||
update_config(|cfg| cfg.ui.show_timeline = Some(value)).await
|
||||
}
|
||||
|
||||
pub async fn set_page_flip_on_send(value: bool) -> Result<()> {
|
||||
update_config(|cfg| cfg.ui.page_flip_on_send = Some(value)).await
|
||||
}
|
||||
|
||||
/// Persist `[ui].simple_mode` via `update_config`. Same `Option<bool>`
|
||||
/// shape as `show_timestamps`.
|
||||
pub async fn set_simple_mode(value: bool) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,33 @@ pub mod hooks;
|
|||
// unchanged.
|
||||
pub use xai_grok_shell_base::util::*;
|
||||
|
||||
pub(crate) fn is_user_instruction_path(
|
||||
path: &std::path::Path,
|
||||
grok_home: &std::path::Path,
|
||||
vendor_homes: &[(std::path::PathBuf, bool)],
|
||||
workspace_root: Option<&std::path::Path>,
|
||||
) -> bool {
|
||||
let parent = path.parent();
|
||||
let grok_rules = grok_home.join("rules");
|
||||
let is_exact_home_surface = parent
|
||||
.is_some_and(|parent| parent == grok_home || parent == grok_rules)
|
||||
|| vendor_homes.iter().any(|(vendor_home, named_enabled)| {
|
||||
parent.is_some_and(|parent| {
|
||||
(*named_enabled && parent == vendor_home) || parent == vendor_home.join("rules")
|
||||
})
|
||||
});
|
||||
if is_exact_home_surface {
|
||||
return true;
|
||||
}
|
||||
if workspace_root.is_some_and(|root| path.starts_with(root)) {
|
||||
return false;
|
||||
}
|
||||
path.starts_with(grok_home)
|
||||
|| vendor_homes
|
||||
.iter()
|
||||
.any(|(vendor_home, _)| path.starts_with(vendor_home))
|
||||
}
|
||||
|
||||
/// Aborts the wrapped tokio task when dropped.
|
||||
///
|
||||
/// Use to tie a spawned helper task's lifetime to an async scope so that
|
||||
|
|
@ -22,3 +49,35 @@ impl Drop for AbortOnDrop {
|
|||
self.0.abort();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod is_user_instruction_path_tests {
|
||||
use super::is_user_instruction_path;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn grok_home_named_file_nested_in_workspace_is_user_scoped() {
|
||||
assert!(is_user_instruction_path(
|
||||
Path::new("/repo/config/AGENTS.md"),
|
||||
Path::new("/repo/config"),
|
||||
&[],
|
||||
Some(Path::new("/repo")),
|
||||
));
|
||||
assert!(!is_user_instruction_path(
|
||||
Path::new("/repo/config/src/AGENTS.md"),
|
||||
Path::new("/repo/config"),
|
||||
&[],
|
||||
Some(Path::new("/repo")),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_descendants_under_grok_home_stay_project_scoped() {
|
||||
assert!(!is_user_instruction_path(
|
||||
Path::new("/custom/grok/worktrees/repo/src/AGENTS.md"),
|
||||
Path::new("/custom/grok"),
|
||||
&[],
|
||||
Some(Path::new("/custom/grok/worktrees/repo")),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue