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
|
|
@ -482,7 +482,7 @@ async fn build_auth(
|
|||
team_role: None,
|
||||
user_blocked_reason: None,
|
||||
team_blocked_reasons: vec![],
|
||||
coding_data_retention_opt_out: false,
|
||||
coding_data_retention_opt_out: crate::auth::default_coding_data_retention_opt_out(),
|
||||
has_grok_code_access: None,
|
||||
refresh_token: tokens.refresh_token.clone(),
|
||||
expires_at: tokens.expires_in.map(|s| now + Duration::seconds(s)),
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ pub(crate) fn parse_output(output: &std::process::Output) -> anyhow::Result<Grok
|
|||
organization_role: None,
|
||||
user_blocked_reason: None,
|
||||
team_blocked_reasons: vec![],
|
||||
coding_data_retention_opt_out: false,
|
||||
coding_data_retention_opt_out: crate::auth::default_coding_data_retention_opt_out(),
|
||||
has_grok_code_access: None,
|
||||
refresh_token,
|
||||
expires_at,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pub struct GateInfo {
|
|||
}
|
||||
|
||||
/// Typed auth metadata passed from the shell to the pager via ACP.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AuthMeta {
|
||||
#[serde(default)]
|
||||
pub email: Option<String>,
|
||||
|
|
@ -26,7 +26,8 @@ pub struct AuthMeta {
|
|||
pub is_zdr: bool,
|
||||
#[serde(default)]
|
||||
pub team_role: Option<String>,
|
||||
#[serde(default)]
|
||||
/// Defaults to opted-out (safer) until auth meta is populated.
|
||||
#[serde(default = "crate::auth::default_coding_data_retention_opt_out")]
|
||||
pub coding_data_retention_opt_out: bool,
|
||||
#[serde(default)]
|
||||
pub show_resolved_model: Option<bool>,
|
||||
|
|
@ -38,3 +39,20 @@ pub struct AuthMeta {
|
|||
#[serde(default)]
|
||||
pub subscription_tier: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for AuthMeta {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
email: None,
|
||||
auth_mode: None,
|
||||
team_id: None,
|
||||
team_name: None,
|
||||
is_zdr: false,
|
||||
team_role: None,
|
||||
coding_data_retention_opt_out: crate::auth::default_coding_data_retention_opt_out(),
|
||||
show_resolved_model: None,
|
||||
gate: None,
|
||||
subscription_tier: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ pub use error::{AuthError, RefreshTokenError, RefreshTokenFailedReason};
|
|||
pub use manager::{AuthManager, shared_api_key_provider};
|
||||
pub use meta::{AuthMeta, GateInfo};
|
||||
pub use model::{AuthMode, GrokAuth, lookup_auth};
|
||||
pub(crate) use model::{TOKEN_TTL, UserInfo, is_expired, token_suffix};
|
||||
pub(crate) use model::{
|
||||
TOKEN_TTL, UserInfo, default_coding_data_retention_opt_out, is_expired, token_suffix,
|
||||
};
|
||||
pub(crate) use refresh::DiagnosticUploader;
|
||||
pub use storage::{
|
||||
clear_api_key, read_api_key, read_auth_json, read_token_by_scope, store_api_key,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,13 @@ pub const API_KEY_SCOPE: &str = "xai::api_key";
|
|||
const BLOCKED_REASON_NO_LOGS: &str = "BLOCKED_REASON_NO_LOGS";
|
||||
const BLOCKED_REASON_NO_LOGS_MODERATED: &str = "BLOCKED_REASON_NO_LOGS_MODERATED";
|
||||
|
||||
/// Fresh-credential / missing-field default: opted out until the user or
|
||||
/// server enrichment opts in. Single source for `GrokAuth`, `AuthMeta`, and
|
||||
/// every login-path constructor so the sides cannot drift.
|
||||
pub(crate) fn default_coding_data_retention_opt_out() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Token provenance (debugging/auth.json only -- no code branches on this).
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
|
@ -69,7 +76,9 @@ pub struct GrokAuth {
|
|||
pub user_blocked_reason: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub team_blocked_reasons: Vec<String>,
|
||||
#[serde(default)]
|
||||
/// Defaults to `true` (opted out) for safer consumer privacy until the
|
||||
/// user explicitly shares or server enrichment sets the team preference.
|
||||
#[serde(default = "default_coding_data_retention_opt_out")]
|
||||
pub coding_data_retention_opt_out: bool,
|
||||
|
||||
/// Deprecated. Kept for deserializing existing auth.json files.
|
||||
|
|
@ -218,7 +227,7 @@ impl Default for GrokAuth {
|
|||
organization_role: None,
|
||||
user_blocked_reason: None,
|
||||
team_blocked_reasons: vec![],
|
||||
coding_data_retention_opt_out: false,
|
||||
coding_data_retention_opt_out: default_coding_data_retention_opt_out(),
|
||||
has_grok_code_access: None,
|
||||
refresh_token: None,
|
||||
expires_at: None,
|
||||
|
|
@ -239,6 +248,9 @@ impl GrokAuth {
|
|||
Self {
|
||||
key: "test-key".into(),
|
||||
user_id: "test-user".into(),
|
||||
// Tests that exercise collection gates need sharing enabled by
|
||||
// default; opt out explicitly when asserting the privacy path.
|
||||
coding_data_retention_opt_out: false,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
@ -486,4 +498,23 @@ mod tests {
|
|||
let info: UserInfo = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(info.subscription_tier.as_deref(), Some(""));
|
||||
}
|
||||
|
||||
/// Pre-default auth.json (no coding_data_retention_opt_out key) must
|
||||
/// deserialize as opted-out, not the old fail-open false.
|
||||
#[test]
|
||||
fn missing_coding_data_retention_opt_out_deserializes_opted_out() {
|
||||
let json = r#"{
|
||||
"key": "k",
|
||||
"auth_mode": "oidc",
|
||||
"create_time": "2020-01-01T00:00:00Z",
|
||||
"user_id": "u"
|
||||
}"#;
|
||||
let auth: GrokAuth = serde_json::from_str(json).unwrap();
|
||||
assert!(
|
||||
auth.coding_data_retention_opt_out,
|
||||
"missing field must default to opted-out"
|
||||
);
|
||||
assert!(default_coding_data_retention_opt_out());
|
||||
assert!(GrokAuth::default().coding_data_retention_opt_out);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -689,7 +689,7 @@ pub(super) async fn validate_and_extract_user_info(
|
|||
organization_role: None,
|
||||
user_blocked_reason: None,
|
||||
team_blocked_reasons: vec![],
|
||||
coding_data_retention_opt_out: false,
|
||||
coding_data_retention_opt_out: crate::auth::default_coding_data_retention_opt_out(),
|
||||
})
|
||||
}
|
||||
pub(super) async fn extract_user_info(
|
||||
|
|
@ -720,7 +720,7 @@ pub(super) async fn extract_user_info(
|
|||
organization_role: None,
|
||||
user_blocked_reason: None,
|
||||
team_blocked_reasons: vec![],
|
||||
coding_data_retention_opt_out: false,
|
||||
coding_data_retention_opt_out: crate::auth::default_coding_data_retention_opt_out(),
|
||||
});
|
||||
}
|
||||
let token = id_token.ok_or_else(|| anyhow::Error::new(OidcError::MissingIdToken))?;
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@ impl TokenRefresher for ExternalBinaryRefresher {
|
|||
.await
|
||||
{
|
||||
Err(_elapsed) => {
|
||||
// Transient: a hard-expired access token after idle must still
|
||||
// allow 401 / pre-flight retry. Mapping timeout to permanent
|
||||
// failure poisoned recovery for PERMANENT_FAILURE_TTL.
|
||||
tracing::warn!(
|
||||
timeout_ms,
|
||||
"auth: external binary refresh timed out (thread leaked)"
|
||||
|
|
@ -70,7 +73,7 @@ impl TokenRefresher for ExternalBinaryRefresher {
|
|||
None,
|
||||
Some(serde_json::json!({ "timeout_ms": timeout_ms })),
|
||||
);
|
||||
self.record_failure(format!("external binary timed out after {timeout_ms}ms"))
|
||||
RefreshOutcome::transient(format!("external binary timed out after {timeout_ms}ms"))
|
||||
}
|
||||
Ok(Ok(Some(auth))) => {
|
||||
crate::unified_log::info("auth: external binary refresh succeeded", None, None);
|
||||
|
|
@ -131,10 +134,8 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// A binary that outlives the (test-shortened) timeout hits the `Elapsed`
|
||||
/// arm and maps to the same non-sticky `Other` permanent failure.
|
||||
#[tokio::test]
|
||||
async fn external_binary_timeout_is_non_sticky_permanent() {
|
||||
async fn external_binary_timeout_is_transient() {
|
||||
struct SlowRunner;
|
||||
impl ExternalCommandRunner for SlowRunner {
|
||||
fn run_external_command(&self, _command: &str) -> Option<GrokAuth> {
|
||||
|
|
@ -145,14 +146,13 @@ mod tests {
|
|||
let refresher = ExternalBinaryRefresher::new(Arc::new(SlowRunner), "auth-binary".into())
|
||||
.with_timeout(std::time::Duration::from_millis(5));
|
||||
match refresher.refresh(RefreshReason::ServerRejected).await {
|
||||
RefreshOutcome::PermanentFailure { error, .. } => {
|
||||
assert_eq!(error.reason, RefreshTokenFailedReason::Other);
|
||||
RefreshOutcome::TransientFailure { message } => {
|
||||
assert!(
|
||||
!error.reason.is_sticky(),
|
||||
"timeout must age out, not strand"
|
||||
message.contains("timed out"),
|
||||
"timeout message must be greppable, got {message}"
|
||||
);
|
||||
}
|
||||
other => panic!("a timed-out binary must be a permanent Other failure, got {other:?}"),
|
||||
other => panic!("a timed-out binary must be TransientFailure, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue