Synced from monorepo
Synced from monorepo Changes: - Release a shell session's resources in one drop - Make the tools blocking-wait cap client-configurable and self-describing - Recognize API "exceeds budget" errors as context overflow - Retry /btw on model overload - Carry running background tasks and subagents across compaction - Require round-trip time for SDK liveness checks - Background-subagent completion reminders with a selectable delivery surface - Make a PTY shell reap itself until it reaches the registry - Recover the OS error code from a TLS-phase connection reset - Consume the attached-client signal and report why idle is withheld - Treat `.grok/sandbox.toml` edits as protected so auto mode prompts before writing - Surface history/search in the Ctrl+. cheatsheet and keep it working in history view - Delete sessions from the dashboard and welcome list - Release a session's activity record when the session ends - Stop charging auth-retry budget for fail-closed 401s; reset it across suspends - Scope skills watches on project vendor roots - Make [stop] cancel in-flight compaction - Make the leader soak measure the leader, not its harness Source-Revision: 8d69c91f02bcacf01e98d5aebbf2f92547c45738
This commit is contained in:
parent
dd04f397b1
commit
a422116582
165 changed files with 15161 additions and 1969 deletions
|
|
@ -105,6 +105,8 @@ async fn leader_soak_churning_clients_no_leaks_no_zombies() {
|
|||
let server = xai_grok_test_support::MockInferenceServer::start()
|
||||
.await
|
||||
.unwrap();
|
||||
// Measure the leader, not the harness's copy of every conversation.
|
||||
server.set_keep_requests(false);
|
||||
let grok_home = TempDir::new().unwrap();
|
||||
let workdir = TempDir::new().unwrap();
|
||||
|
||||
|
|
@ -207,8 +209,6 @@ async fn leader_soak_churning_clients_no_leaks_no_zombies() {
|
|||
let mut turns: u64 = 0;
|
||||
let mut baseline: Option<serde_json::Value> = None;
|
||||
|
||||
// Each cycle: 10 fresh clients, 2 sessions each, one scripted
|
||||
// turn per session, then all disconnect.
|
||||
while tokio::time::Instant::now() < soak_deadline {
|
||||
cycles += 1;
|
||||
let mut clients = Vec::new();
|
||||
|
|
@ -282,7 +282,7 @@ async fn leader_soak_churning_clients_no_leaks_no_zombies() {
|
|||
}
|
||||
|
||||
// An entry that never drains names itself here, one cycle
|
||||
// after it leaks, while memory is still within its budget.
|
||||
// after it leaks.
|
||||
let counts = registry_counts(&mut bootstrap, 1000 + cycles).await;
|
||||
assert_eq!(
|
||||
counts["sessions"], 0,
|
||||
|
|
@ -332,7 +332,7 @@ async fn leader_soak_churning_clients_no_leaks_no_zombies() {
|
|||
({:.2} MB per cycle)",
|
||||
net_bytes as f64 / measured as f64 / (1024.0 * 1024.0)
|
||||
);
|
||||
let max_per_cycle = env_u64("LEADER_SOAK_MAX_HEAP_BYTES_PER_CYCLE", 4 << 20) as i64;
|
||||
let max_per_cycle = env_u64("LEADER_SOAK_MAX_HEAP_BYTES_PER_CYCLE", 1 << 20) as i64;
|
||||
assert!(
|
||||
per_cycle <= max_per_cycle,
|
||||
"leader retained {per_cycle} heap bytes per cycle (bound {max_per_cycle})"
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ const RPC_TIMEOUT: Duration = Duration::from_secs(60);
|
|||
#[serde(deny_unknown_fields)]
|
||||
struct Counts {
|
||||
sessions: usize,
|
||||
loading_sessions: usize,
|
||||
session_threads: usize,
|
||||
resident_resources: usize,
|
||||
retained_resources: usize,
|
||||
|
|
@ -45,6 +46,7 @@ struct Counts {
|
|||
subagent_active: usize,
|
||||
subagent_completed: usize,
|
||||
workspace_bindings: Option<usize>,
|
||||
workspace_activity_sessions: Option<usize>,
|
||||
}
|
||||
struct AutoApproveClient;
|
||||
#[async_trait::async_trait(?Send)]
|
||||
|
|
@ -91,6 +93,19 @@ async fn read_counts(conn: &acp::ClientSideConnection) -> Counts {
|
|||
serde_json::from_value(resp["result"]["registries"].clone())
|
||||
.unwrap_or_else(|e| panic!("x.ai/debug/agent: bad registries payload: {e}\n{resp}"))
|
||||
}
|
||||
/// Counts read once the actor threads are reaped. Nothing signals a thread
|
||||
/// exit, so this polls; both ends settle, so neither catches one mid-exit.
|
||||
async fn settled_counts(conn: &acp::ClientSideConnection) -> Counts {
|
||||
let mut counts = read_counts(conn).await;
|
||||
for _ in 0..100 {
|
||||
if counts.session_threads == 0 {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
counts = read_counts(conn).await;
|
||||
}
|
||||
counts
|
||||
}
|
||||
async fn new_session(conn: &acp::ClientSideConnection, cwd: &std::path::Path) -> acp::SessionId {
|
||||
tokio::time::timeout(
|
||||
RPC_TIMEOUT,
|
||||
|
|
@ -252,21 +267,29 @@ fn session_churn_returns_registry_snapshot_to_baseline() {
|
|||
agent_rt.block_on(local.run_until(async move {
|
||||
let client_conn = connect_and_auth().await;
|
||||
churn_one(&client_conn, workdir.path(), 0).await;
|
||||
let baseline = read_counts(&client_conn).await;
|
||||
let baseline = settled_counts(&client_conn).await;
|
||||
assert_eq!(
|
||||
baseline.sessions, 0,
|
||||
"warmup session must be fully removed before baseline"
|
||||
);
|
||||
assert_eq!(
|
||||
(baseline.resident_resources, baseline.retained_resources),
|
||||
(0, 0),
|
||||
(
|
||||
baseline.resident_resources,
|
||||
baseline.retained_resources,
|
||||
baseline.loading_sessions
|
||||
),
|
||||
(0, 0, 0),
|
||||
"warmup must leave no per-session resource entries, including \
|
||||
entries holding no resources"
|
||||
);
|
||||
assert_eq!(
|
||||
baseline.workspace_bindings,
|
||||
Some(0),
|
||||
"warmup must have built the local workspace and released its binding"
|
||||
(
|
||||
baseline.workspace_bindings,
|
||||
baseline.workspace_activity_sessions
|
||||
),
|
||||
(Some(0), Some(0)),
|
||||
"warmup must have built the local workspace and released both its \
|
||||
binding and its activity record"
|
||||
);
|
||||
assert_eq!(
|
||||
(
|
||||
|
|
@ -295,7 +318,7 @@ fn session_churn_returns_registry_snapshot_to_baseline() {
|
|||
}))
|
||||
.await;
|
||||
futures::future::join_all(concurrent.iter().map(|sid| close_session(conn, sid))).await;
|
||||
let after = read_counts(&client_conn).await;
|
||||
let after = settled_counts(&client_conn).await;
|
||||
assert_eq!(
|
||||
after, baseline,
|
||||
"session churn must return every registry count to baseline \
|
||||
|
|
|
|||
|
|
@ -896,7 +896,7 @@ async fn test_chat_completions_401_unauthorized() {
|
|||
let result = client.conversation_stream(request).await;
|
||||
assert!(result.is_err());
|
||||
|
||||
if let Err(SamplingError::Auth(_)) = result {
|
||||
if let Err(SamplingError::Auth { .. }) = result {
|
||||
// Expected
|
||||
} else {
|
||||
panic!("Expected Auth error");
|
||||
|
|
@ -939,7 +939,7 @@ async fn test_responses_api_401_unauthorized() {
|
|||
let result = client.conversation_stream_responses(request).await;
|
||||
assert!(result.is_err());
|
||||
|
||||
if let Err(SamplingError::Auth(_)) = result {
|
||||
if let Err(SamplingError::Auth { .. }) = result {
|
||||
// Expected
|
||||
} else {
|
||||
panic!("Expected Auth error");
|
||||
|
|
|
|||
Loading…
Reference in a new issue