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:
grokkybara[bot] 2026-07-18 19:48:28 +01:00
commit 7cfcb20d2b
292 changed files with 23315 additions and 9209 deletions

View file

@ -0,0 +1,120 @@
//! Wire e2e + pure tests for user-facing API error sanitization.
//!
//! Edge proxies return non-JSON bodies (HTML). Those must never reach TUI
//! scrollback; only structured JSON error envelopes and status-based copy.
use std::sync::Arc;
use xai_grok_sampler::{SamplerConfig, SamplingClient};
use xai_grok_sampling_types::{
ContentPart, ConversationItem, ConversationRequest, UserItem, status_user_message,
user_facing_api_error_message,
};
use xai_grok_test_support::{MockInferenceServer, ScriptedResponse};
const CF_524_HTML: &str = r#"<!DOCTYPE html>
<html lang="en-US">
<head><title>grok.com | 524: A timeout occurred</title></head>
<body>
<h1>A timeout occurred <span>Error code 524</span></h1>
<div>Visit cloudflare.com for more information.</div>
</body>
</html>"#;
fn test_config(base_url: &str, api_key: &str) -> SamplerConfig {
SamplerConfig {
api_key: Some(api_key.to_string()),
base_url: base_url.to_string(),
model: "test-model".to_string(),
..SamplerConfig::default()
}
}
fn user_request(text: &str) -> ConversationRequest {
ConversationRequest {
items: vec![ConversationItem::User(UserItem {
content: vec![ContentPart::Text {
text: Arc::<str>::from(text),
}],
..Default::default()
})],
..Default::default()
}
}
async fn stream_err(status: u16, body: &str) -> xai_grok_sampling_types::SamplingError {
let server = MockInferenceServer::start().await.expect("start mock");
server.enqueue_response("/v1/chat/completions", ScriptedResponse::text(status, body));
let mut cfg = test_config(&server.url(), "test-key");
cfg.max_retries = Some(0);
let client = SamplingClient::new(cfg).expect("client");
match client.conversation_stream(user_request("hi")).await {
Ok(_) => panic!("expected API error"),
Err(e) => e,
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn stream_524_html_uses_status_copy() {
let err = stream_err(524, CF_524_HTML).await;
let s = err.to_string();
assert!(!s.contains("<!DOCTYPE") && !s.contains("<html"));
assert!(s.contains(&status_user_message(
reqwest::StatusCode::from_u16(524).unwrap()
)));
assert!(s.contains("524"));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn stream_503_html_uses_unavailable_copy() {
let err = stream_err(503, "<html><body>Service Unavailable</body></html>").await;
let s = err.to_string();
assert!(!s.contains("<html"));
assert!(s.contains("temporarily unavailable"));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn stream_json_error_envelope_is_preserved() {
let body = r#"{"error":{"message":"rate limit exceeded","type":"rate_limit_error"}}"#;
let err = stream_err(429, body).await;
let s = err.to_string();
assert!(s.contains("rate limit exceeded"));
assert!(!s.contains("temporarily unavailable"));
}
#[test]
fn status_user_message_matrix() {
let cases: &[(u16, &str)] = &[
(502, "temporarily unavailable"),
(503, "temporarily unavailable"),
(504, "temporarily unavailable"),
(520, "timed out"),
(524, "timed out"),
(500, "Something went wrong on the server"),
(400, "Request failed"),
];
for &(code, needle) in cases {
let msg = status_user_message(reqwest::StatusCode::from_u16(code).unwrap());
assert!(
msg.contains(needle),
"status {code}: expected {needle:?} in {msg:?}"
);
assert!(
msg.contains(&format!("HTTP {code}")),
"status {code}: expected HTTP code in {msg:?}"
);
}
}
#[test]
fn non_json_empty_body_falls_back_to_status() {
let msg = user_facing_api_error_message(reqwest::StatusCode::BAD_GATEWAY, b"");
assert_eq!(msg, status_user_message(reqwest::StatusCode::BAD_GATEWAY));
}
#[test]
fn structured_json_is_not_replaced_by_status_copy() {
let bytes = br#"{"error":{"message":"credits exhausted","type":"server_error"}}"#;
let msg = user_facing_api_error_message(reqwest::StatusCode::PAYMENT_REQUIRED, bytes);
assert_eq!(msg, "credits exhausted");
}