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:
parent
3af4d5d398
commit
a5727c5960
482 changed files with 37627 additions and 13402 deletions
|
|
@ -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]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue