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
|
|
@ -905,6 +905,66 @@ pub struct ToolServerStatusPayload {
|
|||
/// tasks.
|
||||
#[serde(default)]
|
||||
pub idle_ignores_background: bool,
|
||||
/// Why `idle_since_ms` is being withheld, when it is. `None` ⇒ not withheld
|
||||
/// (or the tool server is genuinely busy, which `active_tool_calls`
|
||||
/// reports).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub withhold_reason: Option<IdleWithholdReason>,
|
||||
/// Epoch ms the current hold is measured from. Never `None` while
|
||||
/// `withhold_reason` is `Some`.
|
||||
///
|
||||
/// NOT the instant the withhold began, for the preview reasons. It is the
|
||||
/// **real-use anchor**: the last routed request or tool call, floored at
|
||||
/// process start. A status poll never moves it, which is the point — a
|
||||
/// ceiling has to be measured from genuine use, or the pane could hold a
|
||||
/// sandbox open forever by resetting the clock it is judged against. So
|
||||
/// for a poll-pinned session this is *older* than the poll-only period,
|
||||
/// and `now - withhold_since_ms` is time-since-real-use, not
|
||||
/// time-spent-withholding. `Durability` is the exception: there it is the
|
||||
/// true busy-since stamp.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub withhold_since_ms: Option<u64>,
|
||||
/// `true` once the current hold has crossed its effective ceiling. The
|
||||
/// verdict is published rather than re-derived because the ceiling is
|
||||
/// per-session config only the sender can see. Always `false` today: no
|
||||
/// ceilings are configured yet.
|
||||
#[serde(default)]
|
||||
pub withhold_capped: bool,
|
||||
/// Open WebSocket (HMR) tunnels through the in-sandbox preview proxy.
|
||||
/// Nonzero ⇒ a client is attached even if the preview is otherwise silent.
|
||||
#[serde(default)]
|
||||
pub preview_ws_tunnels_open: u32,
|
||||
}
|
||||
|
||||
/// Why a tool server is withholding `idle_since_ms`, ordered by strength of
|
||||
/// evidence that someone is really using the sandbox.
|
||||
///
|
||||
/// Carries an [`Unknown`](Self::Unknown) escape so adding a variant cannot
|
||||
/// break older readers: without it, one unrecognised string would fail
|
||||
/// deserialization of the **entire** status frame, taking the idle verdict down
|
||||
/// with it. Sandbox binaries are pinned per session, so old and new report side
|
||||
/// by side for at least a full session TTL.
|
||||
///
|
||||
/// Deliberately not `#[non_exhaustive]`: in-workspace matches should stay
|
||||
/// exhaustive so a new variant is a compile error at every decision site, which
|
||||
/// is a different problem from wire tolerance.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum IdleWithholdReason {
|
||||
/// Artifact producers or queued uploads outstanding. Already bounded by the
|
||||
/// durability idle-hold cap.
|
||||
Durability,
|
||||
/// An open WebSocket tunnel or a routed request in flight. Never a status
|
||||
/// poll, however long it is held.
|
||||
PreviewAttached,
|
||||
/// Recent `Routed` preview traffic — a human loading the app.
|
||||
PreviewRouted,
|
||||
/// Only the preview pane's own `/__grok-preview/status` liveness poll.
|
||||
PreviewStatusOnly,
|
||||
/// A reason this build does not recognise — a newer sender. Never
|
||||
/// constructed locally; only produced by deserialization.
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl ToolServerStatusPayload {
|
||||
|
|
@ -1336,6 +1396,10 @@ mod tests {
|
|||
drain_started_ms: Some(1721234599999),
|
||||
turn_active: true,
|
||||
idle_ignores_background: false,
|
||||
withhold_reason: None,
|
||||
withhold_since_ms: None,
|
||||
withhold_capped: false,
|
||||
preview_ws_tunnels_open: 0,
|
||||
};
|
||||
let json = serde_json::to_value(&payload).expect("serialize");
|
||||
assert_eq!(json["upload_queue_pending"], 7);
|
||||
|
|
@ -1350,6 +1414,81 @@ mod tests {
|
|||
assert_eq!(back, payload);
|
||||
}
|
||||
|
||||
/// `withhold_reason` is snake_case on the wire so it can be used directly
|
||||
/// as a metric label.
|
||||
#[test]
|
||||
fn tool_server_status_payload_carries_withhold_fields() {
|
||||
let payload = super::ToolServerStatusPayload {
|
||||
status: super::ToolServerLifecycleStatus::Ready,
|
||||
session_id: Some(sid()),
|
||||
idle_since_ms: None,
|
||||
withhold_reason: Some(super::IdleWithholdReason::PreviewStatusOnly),
|
||||
withhold_since_ms: Some(1721234560000),
|
||||
withhold_capped: true,
|
||||
preview_ws_tunnels_open: 2,
|
||||
..Default::default()
|
||||
};
|
||||
let json = serde_json::to_value(&payload).expect("serialize");
|
||||
assert_eq!(json["withhold_reason"], "preview_status_only");
|
||||
assert_eq!(json["withhold_since_ms"], 1721234560000u64);
|
||||
assert_eq!(json["withhold_capped"], true);
|
||||
assert_eq!(json["preview_ws_tunnels_open"], 2);
|
||||
let back: super::ToolServerStatusPayload =
|
||||
serde_json::from_value(json).expect("deserialize");
|
||||
assert_eq!(back, payload);
|
||||
}
|
||||
|
||||
/// An unrecognised reason from a newer sender must degrade to `Unknown`,
|
||||
/// not fail the whole frame and take the idle verdict with it.
|
||||
#[test]
|
||||
fn unknown_withhold_reason_does_not_fail_the_frame() {
|
||||
let json = serde_json::json!({
|
||||
"status": "ready",
|
||||
"active_tool_calls": 0,
|
||||
"background_tasks": 0,
|
||||
"pending_tool_calls": 0,
|
||||
"last_tool_call_started_ms": 0,
|
||||
"last_tool_call_completed_ms": 0,
|
||||
"uptime_ms": 1000,
|
||||
"withhold_reason": "some_future_reason",
|
||||
"withhold_since_ms": 1721234560000u64,
|
||||
});
|
||||
let back: super::ToolServerStatusPayload =
|
||||
serde_json::from_value(json).expect("a newer reason must not break the frame");
|
||||
assert_eq!(
|
||||
back.withhold_reason,
|
||||
Some(super::IdleWithholdReason::Unknown)
|
||||
);
|
||||
assert_eq!(
|
||||
back.withhold_since_ms,
|
||||
Some(1721234560000),
|
||||
"the rest of the frame must survive intact"
|
||||
);
|
||||
}
|
||||
|
||||
/// The fleet is version-pinned per session, so old and new binaries report
|
||||
/// side by side for at least a full session TTL.
|
||||
#[test]
|
||||
fn tool_server_status_payload_withhold_fields_are_optional_on_the_wire() {
|
||||
let json = serde_json::json!({
|
||||
"status": "ready",
|
||||
"active_tool_calls": 0,
|
||||
"background_tasks": 0,
|
||||
"pending_tool_calls": 0,
|
||||
"last_tool_call_started_ms": 0,
|
||||
"last_tool_call_completed_ms": 0,
|
||||
"uptime_ms": 1000,
|
||||
});
|
||||
let back: super::ToolServerStatusPayload =
|
||||
serde_json::from_value(json).expect("old payload must still deserialize");
|
||||
assert_eq!(back.withhold_reason, None);
|
||||
assert_eq!(back.withhold_since_ms, None);
|
||||
assert!(!back.withhold_capped);
|
||||
assert_eq!(back.preview_ws_tunnels_open, 0);
|
||||
// An absent reason is indistinguishable from "nothing is withheld" —
|
||||
// what the field-coverage ratio exists to measure.
|
||||
}
|
||||
|
||||
/// A legacy payload without the new fields deserializes with defaults.
|
||||
#[test]
|
||||
fn tool_server_status_payload_legacy_without_pr9_fields_defaults() {
|
||||
|
|
|
|||
|
|
@ -38,21 +38,21 @@ pub use error_codes::{
|
|||
};
|
||||
pub use error_wire::ToolErrorWire;
|
||||
pub use frames::{
|
||||
AttachRoute, HookFrame, HookReplyFrame, LastSeq, LogsDonateParams, MAX_DONATION_BYTES,
|
||||
MAX_LOG_RECORDS_PER_DONATION, MAX_METRICS_PER_DONATION, MAX_SPANS_PER_DONATION,
|
||||
MAX_SYSTEM_NOTIFY_PAYLOAD_BYTES, MetricsDonateParams, NotificationFilter, PingFrame, PongFrame,
|
||||
ServeParams, ServeResult, ServerBindAck, ServerBindOutcome, ServerBindParams, ServerInfo,
|
||||
ServerUnbindAck, ServerUnbindOutcome, ServerUnbindParams, ServersListParams, ServersListResult,
|
||||
SessionAttachServerParams, SessionAttachServerResult, SessionBindParams, SessionBindResult,
|
||||
SessionBindServerParams, SessionBindServerResult, SessionCloseParams, SessionOpenParams,
|
||||
SessionOpenResult, SessionUnbindParams, SessionUnbindServerParams, SubscribeAck,
|
||||
SubscribeNotificationsParams, SubscribeOutcome, SystemNotifyParams, ToolCallParams,
|
||||
ToolCallProgressFrame, ToolCallResult, ToolNotificationFrame, ToolSearchResult,
|
||||
ToolServerConnectionStatus, ToolServerDisconnectReason, ToolServerEvictParams,
|
||||
ToolServerGetStatusParams, ToolServerGetStatusResult, ToolServerLifecycleStatus,
|
||||
ToolServerStatusPayload, ToolsChanged, ToolsListParams, ToolsListResult, ToolsSearchParams,
|
||||
ToolsSearchResultBody, TracesDonateParams, UnsubscribeAck, UnsubscribeNotificationsParams,
|
||||
UnsubscribeOutcome,
|
||||
AttachRoute, HookFrame, HookReplyFrame, IdleWithholdReason, LastSeq, LogsDonateParams,
|
||||
MAX_DONATION_BYTES, MAX_LOG_RECORDS_PER_DONATION, MAX_METRICS_PER_DONATION,
|
||||
MAX_SPANS_PER_DONATION, MAX_SYSTEM_NOTIFY_PAYLOAD_BYTES, MetricsDonateParams,
|
||||
NotificationFilter, PingFrame, PongFrame, ServeParams, ServeResult, ServerBindAck,
|
||||
ServerBindOutcome, ServerBindParams, ServerInfo, ServerUnbindAck, ServerUnbindOutcome,
|
||||
ServerUnbindParams, ServersListParams, ServersListResult, SessionAttachServerParams,
|
||||
SessionAttachServerResult, SessionBindParams, SessionBindResult, SessionBindServerParams,
|
||||
SessionBindServerResult, SessionCloseParams, SessionOpenParams, SessionOpenResult,
|
||||
SessionUnbindParams, SessionUnbindServerParams, SubscribeAck, SubscribeNotificationsParams,
|
||||
SubscribeOutcome, SystemNotifyParams, ToolCallParams, ToolCallProgressFrame, ToolCallResult,
|
||||
ToolNotificationFrame, ToolSearchResult, ToolServerConnectionStatus,
|
||||
ToolServerDisconnectReason, ToolServerEvictParams, ToolServerGetStatusParams,
|
||||
ToolServerGetStatusResult, ToolServerLifecycleStatus, ToolServerStatusPayload, ToolsChanged,
|
||||
ToolsListParams, ToolsListResult, ToolsSearchParams, ToolsSearchResultBody, TracesDonateParams,
|
||||
UnsubscribeAck, UnsubscribeNotificationsParams, UnsubscribeOutcome,
|
||||
};
|
||||
pub use handshake::{HelloAckMsg, HelloMsg, PROTOCOL_VERSION};
|
||||
pub use hook::HookEvent;
|
||||
|
|
|
|||
Loading…
Reference in a new issue