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

@ -194,7 +194,8 @@ pub(crate) fn enforce_login_principal(
format!("one of teams: {}", allowed.join(", "))
};
tracing::warn!(
expected = % expected, actual = ? actual,
expected = %expected,
actual = ?actual,
"OIDC: login principal does not satisfy required policy; rejecting"
);
Err(anyhow::Error::new(OidcError::PinnedPrincipalMismatch {
@ -303,7 +304,7 @@ fn discovery_retry_policy() -> backon::ExponentialBuilder {
}
async fn discover_once(issuer_key: &str) -> anyhow::Result<Discovery> {
let url = format!("{issuer_key}/.well-known/openid-configuration");
tracing::debug!(url = % url, "OIDC: fetching discovery document");
tracing::debug!(url = %url, "OIDC: fetching discovery document");
let resp = with_alpha_test_key(
crate::http::shared_client()
.get(&url)
@ -320,9 +321,11 @@ async fn discover_once(issuer_key: &str) -> anyhow::Result<Discovery> {
}
let doc: Discovery = resp.json().await?;
tracing::debug!(
authorization_endpoint = % doc.authorization_endpoint, token_endpoint = % doc
.token_endpoint, jwks_uri = ? doc.jwks_uri, id_token_algs = ? doc
.id_token_signing_alg_values_supported, "OIDC: discovery complete"
authorization_endpoint = %doc.authorization_endpoint,
token_endpoint = %doc.token_endpoint,
jwks_uri = ?doc.jwks_uri,
id_token_algs = ?doc.id_token_signing_alg_values_supported,
"OIDC: discovery complete"
);
Ok(doc)
}
@ -405,9 +408,7 @@ pub(super) async fn exchange_code(
client_id: &str,
code_verifier: &str,
) -> anyhow::Result<TokenResponse> {
tracing::debug!(
token_endpoint = % token_endpoint, "OIDC: exchanging code for tokens"
);
tracing::debug!(token_endpoint = %token_endpoint, "OIDC: exchanging code for tokens");
let resp = with_alpha_test_key(
crate::http::shared_client()
.post(token_endpoint)
@ -472,8 +473,10 @@ pub(super) async fn refresh_tokens(
) -> anyhow::Result<TokenResponse> {
use backon::Retryable;
tracing::debug!(
token_endpoint = % token_endpoint, principal_type = ? principal_type,
principal_id = ? principal_id, "OIDC: refreshing token"
token_endpoint = %token_endpoint,
principal_type = ?principal_type,
principal_id = ?principal_id,
"OIDC: refreshing token"
);
(|| {
refresh_tokens_once(
@ -525,9 +528,12 @@ async fn refresh_tokens_once(
.ok()
.and_then(|v| v.get("error")?.as_str().map(str::to_owned));
tracing::warn!(
http_status = status, oauth2_error = ? error_code, rt_prefix = crate
::auth::token_suffix(refresh_token), client_id = % client_id, principal_type
= ? principal_type, "OIDC: token refresh HTTP error"
http_status = status,
oauth2_error = ?error_code,
rt_prefix = crate::auth::token_suffix(refresh_token),
client_id = %client_id,
principal_type = ?principal_type,
"OIDC: token refresh HTTP error"
);
return Err(anyhow::Error::new(OidcError::TokenRefreshHttp {
status,
@ -566,9 +572,7 @@ pub(super) fn aud_matches(aud: &serde_json::Value, expected: &str) -> bool {
}
pub(super) fn validate_state(expected: &str, received: &str) -> anyhow::Result<()> {
if received != expected {
tracing::warn!(
expected = % expected, received = % received, "OIDC: state mismatch"
);
tracing::warn!(expected = %expected, received = %received, "OIDC: state mismatch");
return Err(anyhow::Error::new(OidcError::StateMismatch));
}
Ok(())
@ -993,23 +997,31 @@ mod tests {
)
.unwrap()
}
let team_jwt = make_jwt(serde_json::json!(
{ "sub" : "user-42", "iss" : "https://auth.x.ai", "aud" : "test-client",
"exp" : 9999999999u64, "iat" : 1000000000u64, "scope" :
"offline_access grok-cli:access api:access", "principal_type" : "Team",
"principal_id" : "team-abc-123", "client_id" : "test-client", "jti" :
"token-1", }
));
let team_jwt = make_jwt(serde_json::json!({
"sub": "user-42",
"iss": "https://auth.x.ai",
"aud": "test-client",
"exp": 9999999999u64,
"iat": 1000000000u64,
"scope": "offline_access grok-cli:access api:access",
"principal_type": "Team",
"principal_id": "team-abc-123",
"client_id": "test-client",
"jti": "token-1",
}));
let (pt, pid, tid) = peek_access_token_principal(&team_jwt).expect("team principal");
assert_eq!(pt, "Team");
assert_eq!(pid, "team-abc-123");
assert_eq!(tid, None);
assert!(peek_access_token_principal("not-a-jwt-token").is_none());
assert!(peek_access_token_principal("").is_none());
let no_principal = make_jwt(serde_json::json!(
{ "sub" : "user-42", "iss" : "https://auth.x.ai", "aud" : "test-client",
"exp" : 9999999999u64, "iat" : 1000000000u64, }
));
let no_principal = make_jwt(serde_json::json!({
"sub": "user-42",
"iss": "https://auth.x.ai",
"aud": "test-client",
"exp": 9999999999u64,
"iat": 1000000000u64,
}));
assert!(peek_access_token_principal(&no_principal).is_none());
}
/// `peek_access_token_principal_id` extracts the id even when
@ -1026,7 +1038,7 @@ mod tests {
)
.unwrap()
}
let id_only = make_jwt(serde_json::json!({ "principal_id" : "team-abc", "sub" : "u" }));
let id_only = make_jwt(serde_json::json!({ "principal_id": "team-abc", "sub": "u" }));
assert_eq!(
peek_access_token_principal_id(&id_only).as_deref(),
Some("team-abc"),
@ -1035,7 +1047,7 @@ mod tests {
peek_access_token_principal(&id_only).is_none(),
"the strict peek still needs principal_type",
);
let none = make_jwt(serde_json::json!({ "sub" : "u" }));
let none = make_jwt(serde_json::json!({ "sub": "u" }));
assert!(peek_access_token_principal_id(&none).is_none());
assert!(peek_access_token_principal_id("not-a-jwt").is_none());
}
@ -1113,10 +1125,10 @@ mod tests {
let counter = hits_for_handler.clone();
async move {
counter.fetch_add(1, Ordering::SeqCst);
axum::Json(serde_json::json!(
{ "authorization_endpoint" : format!("{b}/authorize"),
"token_endpoint" : format!("{b}/token"), }
))
axum::Json(serde_json::json!({
"authorization_endpoint": format!("{b}/authorize"),
"token_endpoint": format!("{b}/token"),
}))
}
}),
);