Synced from monorepo

Changes:
- Non-blocking coding-data sharing upsell banner
- Consolidate remediation in Doctor
- Auto mode defers fail-closed gate asks to the classifier
- Coalesce marketplace list fetches
- Allow removing a marketplace source by name
- Contain hung git marketplace sources (timeouts, non-blocking refresh, unbrick modal)
- Label failed workspace RPCs with error_kind
- Drop redundant explicit tonic/prost deps from xai-grok-shell
- Report real exit codes for completed background shells
- Narrow the date-rollover reminder to date-bearing templates
- Wire toolOverrides through the session and agent
- Security: Bash(git:*) allowlist matches whole command chain by prefix
- Split prompt-trigger telemetry and record classifier provenance
- Raise connectors-manager timeout to 60s
- Auto classifier honors recorded approvals for repeat actions
- Apply doctor fixes in the TUI
- Auto-mode classifier timeouts prompt instead of silently denying
- Scope subagent completion drains to the owning session
- Add the toolOverrides wire types
- Set client_identifier=grok-agent-sdk
- Accept both spellings of the workspace-teleport kill switch
- Persist one-shot occurrence journal
- Stop turns that poll the exact same tool call 16x in a row
- Copy compaction checkpoint files when forking sessions
- Auto-focus permission prompt from scrollback
- Esc cancels the running turn in non-vim and minimal modes
- List Ctrl+Z undo and redo in keyboard shortcuts
- Out-of-process macOS mic capture
- Show active auth mode on session-info
- Install the npm binary under $GROK_HOME
- Remove hover/click dead zones between dashboard items
- Route startup warnings to doctor
- Document [feedback.user] author identity config
- Extend bang command timeout
- Close combine-queued edit-hold race
- Integrate relocation recovery
- Expose privacy notice rollout flag
- Break harness discovery ref cycle so connections can idle-evict
- Shift/Alt+Enter inserts newline when editing a queued prompt
- Gate project Claude permissions on folder trust
- Echo response.create.event_id on response.created
- Toast when session creation fails from disk full
- Add shared test process lifecycle
- Enable dynamic workflows by default
- Add relocation transaction state machine
- Add shared test sandbox
- Surface auth failures on model-switch compact
- Persist durable scheduler expiry
- Confirm before removing extensions-modal items
- Re-run compact and prompt after login when compact hit expired auth
- Recap sends hosted tools under backend search
This commit is contained in:
grokkybara[bot] 2026-07-22 19:18:53 +01:00
commit a5727c5960
482 changed files with 37627 additions and 13402 deletions

View file

@ -93,7 +93,11 @@ pub fn load_config_from_toml(root: &TomlValue) -> Config {
cli: section(table, "cli"),
models: section(table, "models"),
ui: section(table, "ui"),
harness: section(table, "harness"),
harness: {
#[allow(unused_mut)]
let mut harness: crate::agent::config::HarnessConfig = section(table, "harness");
harness
},
skills: section(table, "skills"),
compat: section(table, "compat"),
management_api_key,
@ -105,6 +109,7 @@ pub fn load_config_from_toml(root: &TomlValue) -> Config {
.and_then(|t| t.get("ask_user_question"))
.and_then(|v| v.clone().try_into().ok())
.unwrap_or_default(),
privacy: section(table, "privacy"),
}
}
/// Resolve permission config with project override semantics.
@ -130,7 +135,7 @@ pub async fn resolve_permission_config(
tracing::info!("Loaded [permission] from project");
return Some((perm_config, config_path));
}
Err(e) => tracing::warn!(error = % e, "Failed to parse [permission]"),
Err(e) => tracing::warn!(error = %e, "Failed to parse [permission]"),
}
}
}

View file

@ -49,6 +49,16 @@ pub struct Config {
/// the settings modal writes; the rest of `[toolset]` never round-trips
/// (it carries runtime-only structs whose defaults must not hit disk).
pub ask_user_question: crate::tools::config::AskUserQuestionToolConfig,
/// `[privacy]` — local banner ack (not auth-metadata).
pub privacy: PrivacyConfig,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct PrivacyConfig {
/// Last banner dismiss (Accept/Customize), RFC 3339 UTC. None/0 remote
/// `privacy_banner_reshow_days` = never re-show once set.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub privacy_banner_acked: Option<String>,
}
pub fn get_mcp_server_config(name: &str) -> Option<McpServerConfig> {

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,10 @@ use toml::Value as TomlValue;
/// Env override for the **auto** permission-mode feature gate.
pub(crate) const ENV_AUTO_PERMISSION_MODE: &str = "GROK_AUTO_PERMISSION_MODE";
const AUTO_MODE_CLASSIFY_TIMEOUT_MIN_MS: u64 = 1_000;
const AUTO_MODE_CLASSIFY_TIMEOUT_DEFAULT_MS: u64 = 30_000;
const AUTO_MODE_CLASSIFY_TIMEOUT_MAX_MS: u64 = 120_000;
/// Crate-wide serialization lock for tests that mutate
/// `GROK_AUTO_PERMISSION_MODE`. Every test reading the gate (here and in
/// `permissions.rs`, compiled into the same test binary) locks this so a
@ -163,6 +167,7 @@ fn merge_auto_mode_config(
enabled: config.enabled.or(remote.enabled),
prompt_type: config.prompt_type.or(remote.prompt_type),
classifier_model: config.classifier_model.or(remote.classifier_model),
classify_timeout_ms: config.classify_timeout_ms.or(remote.classify_timeout_ms),
reasoning_effort: config.reasoning_effort.or(remote.reasoning_effort),
}
}
@ -184,6 +189,28 @@ pub fn resolve_auto_mode_config_from_disk() -> crate::agent::config::AutoModeCon
merge_auto_mode_config(config, remote)
}
pub fn auto_mode_classify_timeout(
cfg: &crate::agent::config::AutoModeConfig,
) -> std::time::Duration {
let configured = cfg
.classify_timeout_ms
.unwrap_or(AUTO_MODE_CLASSIFY_TIMEOUT_DEFAULT_MS);
let bounded = configured.clamp(
AUTO_MODE_CLASSIFY_TIMEOUT_MIN_MS,
AUTO_MODE_CLASSIFY_TIMEOUT_MAX_MS,
);
if bounded != configured {
tracing::warn!(
configured_ms = configured,
bounded_ms = bounded,
min_ms = AUTO_MODE_CLASSIFY_TIMEOUT_MIN_MS,
max_ms = AUTO_MODE_CLASSIFY_TIMEOUT_MAX_MS,
"[auto_mode] classify_timeout_ms outside supported range; clamped"
);
}
std::time::Duration::from_millis(bounded)
}
/// Apply the built-in Auto-mode classifier defaults to a resolved config (these
/// take effect once auto mode is enabled): an unset `prompt_type` defaults to
/// `full` (v9-traffic eval: transcript context cuts the residual block rate
@ -403,22 +430,69 @@ mod auto_permission_mode_gate_tests {
enabled: Some(true),
prompt_type: Some(ClassifierPromptType::JustCommand),
classifier_model: None,
classify_timeout_ms: Some(45_000),
reasoning_effort: None,
};
let remote = AutoModeConfig {
enabled: Some(false),
prompt_type: Some(ClassifierPromptType::Full),
classifier_model: Some("remote-model".into()),
classify_timeout_ms: Some(60_000),
reasoning_effort: Some(ReasoningEffort::Low),
};
let merged = merge_auto_mode_config(config, remote);
assert_eq!(merged.enabled, Some(true));
assert_eq!(merged.prompt_type, Some(ClassifierPromptType::JustCommand));
assert_eq!(merged.classifier_model.as_deref(), Some("remote-model"));
assert_eq!(merged.classify_timeout_ms, Some(45_000));
assert_eq!(merged.reasoning_effort, Some(ReasoningEffort::Low));
let remote_timeout = merge_auto_mode_config(
AutoModeConfig::default(),
AutoModeConfig {
classify_timeout_ms: Some(60_000),
..AutoModeConfig::default()
},
);
assert_eq!(remote_timeout.classify_timeout_ms, Some(60_000));
// Both unset ⇒ all-None (the wire fn then applies the built-in defaults).
let empty = merge_auto_mode_config(AutoModeConfig::default(), AutoModeConfig::default());
assert!(empty.enabled.is_none() && empty.classifier_model.is_none());
assert_eq!(empty.enabled, None);
assert_eq!(empty.prompt_type, None);
assert_eq!(empty.classifier_model, None);
assert_eq!(empty.classify_timeout_ms, None);
assert_eq!(empty.reasoning_effort, None);
}
#[test]
fn auto_mode_classify_timeout_applies_default_and_bounds() {
use crate::agent::config::AutoModeConfig;
use std::time::Duration;
assert_eq!(
auto_mode_classify_timeout(&AutoModeConfig::default()),
Duration::from_millis(AUTO_MODE_CLASSIFY_TIMEOUT_DEFAULT_MS)
);
assert_eq!(
auto_mode_classify_timeout(&AutoModeConfig {
classify_timeout_ms: Some(45_000),
..AutoModeConfig::default()
}),
Duration::from_millis(45_000)
);
assert_eq!(
auto_mode_classify_timeout(&AutoModeConfig {
classify_timeout_ms: Some(0),
..AutoModeConfig::default()
}),
Duration::from_millis(AUTO_MODE_CLASSIFY_TIMEOUT_MIN_MS)
);
assert_eq!(
auto_mode_classify_timeout(&AutoModeConfig {
classify_timeout_ms: Some(u64::MAX),
..AutoModeConfig::default()
}),
Duration::from_millis(AUTO_MODE_CLASSIFY_TIMEOUT_MAX_MS)
);
}
#[test]
@ -450,13 +524,14 @@ mod auto_permission_mode_gate_tests {
use xai_grok_workspace::permission::ClassifierPromptType;
// A real [auto_mode] table round-trips (not silently dropped).
let toml: TomlValue = toml::from_str(
"[auto_mode]\nenabled = true\nprompt_type = \"just_command\"\nclassifier_model = \"m\"\n",
"[auto_mode]\nenabled = true\nprompt_type = \"just_command\"\nclassifier_model = \"m\"\nclassify_timeout_ms = 45000\n",
)
.unwrap();
let cfg = auto_mode_config_from_toml(Some(&toml)).expect("table parses");
assert_eq!(cfg.enabled, Some(true));
assert_eq!(cfg.prompt_type, Some(ClassifierPromptType::JustCommand));
assert_eq!(cfg.classifier_model.as_deref(), Some("m"));
assert_eq!(cfg.classify_timeout_ms, Some(45_000));
// Absent [auto_mode] ⇒ None.
let bare: TomlValue = toml::from_str("[features]\ngoal = true\n").unwrap();
assert!(auto_mode_config_from_toml(Some(&bare)).is_none());
@ -470,11 +545,12 @@ mod auto_permission_mode_gate_tests {
use xai_grok_workspace::permission::ClassifierPromptType;
let _g = guard();
// Seed the full remote config, then flip ONLY the gate via the pager
// kill-switch path — prompt_type / classifier_model must survive.
// kill-switch path — classifier fields must survive.
cache_remote_auto_mode(Some(serde_json::json!({
"enabled": true,
"prompt_type": "bare_instructions",
"classifier_model": "remote-model"
"classifier_model": "remote-model",
"classify_timeout_ms": 45000
})));
assert_eq!(cached_remote_auto_permission_mode_enabled(), Some(true));
cache_remote_auto_permission_mode_enabled(Some(false));
@ -489,6 +565,7 @@ mod auto_permission_mode_gate_tests {
Some(ClassifierPromptType::BareInstructions)
);
assert_eq!(stored.classifier_model.as_deref(), Some("remote-model"));
assert_eq!(stored.classify_timeout_ms, Some(45_000));
cache_remote_auto_mode(None);
}
}

View file

@ -117,6 +117,14 @@ pub async fn set_default_model(value: String) -> Result<()> {
.await
}
/// Persist `[privacy].privacy_banner_acked` (RFC 3339 UTC dismiss time).
pub async fn set_privacy_banner_acked(acked_at_rfc3339: String) -> Result<()> {
update_config(|cfg| {
cfg.privacy.privacy_banner_acked = Some(acked_at_rfc3339);
})
.await
}
/// Persist `[ui].fork_secondary_model` via `update_config`.
///
/// Caller must validate against the model catalog. Empty string

View file

@ -108,10 +108,7 @@ impl GrokAuthCredentials {
creds
}
Err(e) => {
tracing::warn!(
error = % e,
"resolve_credentials_async: active resolve failed, using cached"
);
tracing::warn!(error = %e, "resolve_credentials_async: active resolve failed, using cached");
self.resolve()
}
}