Synced from monorepo

Synced from monorepo

Changes:
- Refresh tool search when the managed MCP catalog is re-fetched
- Prevent duplicate leader process spawn and startup hang from stale leaders
- Document marketplaces, plugins, and organization controls
- Stamp session ID on image generation direct-to-API requests
- Fix auto mode blocked documentation
- Auto mode considers recent user intent
- Expose deploy archive, taken-down, limit, and in-progress reasons on the chat API
- Fail-closed auth refresh contract for shell clients
- Emit a chat-supplied per-session turn index in turn hooks
- Show bash mode chrome in minimal mode
- Add metrics for true-noop and stationarity stops
- Include voice interim text on prompt submit
- Silently end turn on true-noop thrash
- Quiet copy toast when clipboard delivery is confirmed
- Fix session fork truncating at the wrong prompt in rewound sessions
- Make the idle "still running" watcher cue clickable to open the tasks pane
- Default web search model to grok-4.5
- Let plugin subagents inherit parent MCP servers
- Gate no-op end-turn reminder on system reminders
- Add gateway bridge lifecycle telemetry
- Allow editing finalized text while voice is open
- Relocate token carrier to turn-commit events and plumb per-turn origin context
- Raise workflow scratch quotas and make failed runs resumable
- Workflows overlay: auto-progress phases, live agent status, and drop budget meter

Source-Revision: 9b8d35b46d959c042ea9aa31cbbebbd1f0c5c527
This commit is contained in:
grokkybara[bot] 2026-07-24 16:59:42 +00:00
commit 6e38642082
103 changed files with 4964 additions and 1261 deletions

View file

@ -456,7 +456,11 @@ fn is_pure_status_print(trimmed: &str) -> bool {
/// - Normal: `exit: N [annotations]\n<stripped_output>`
/// - Killed by harness/signal: `exit: killed (reason) [annotations]\n<stripped_output>`
/// - Backgrounded: verbose `[Command moved to background]...` format.
pub(crate) fn format_default_prompt(bash: &BashOutput) -> String {
///
/// `append_noop_reminder` gates the no-op-command end-turn `<system-reminder>`.
/// Callers pass the session's `SystemRemindersEnabled` value so the nudge
/// follows the same switch as every other system reminder.
pub(crate) fn format_default_prompt(bash: &BashOutput, append_noop_reminder: bool) -> String {
let output_str = if bash.output_for_prompt.is_empty() {
let raw = String::from_utf8_lossy(&bash.output);
strip_ansi_escapes::strip_str(&raw).to_string()
@ -487,7 +491,7 @@ pub(crate) fn format_default_prompt(bash: &BashOutput) -> String {
None => format!("exit: {}{}", bash.exit_code, annotations(bash)),
};
let prompt = format!("{}\n{}", header, output_str);
if bash.signal.is_none() && is_noop_command(&bash.command) {
if append_noop_reminder && bash.signal.is_none() && is_noop_command(&bash.command) {
format!("{}\n\n{}", prompt.trim_end(), NOOP_END_TURN_REMINDER)
} else {
prompt
@ -2242,7 +2246,16 @@ impl xai_tool_runtime::Tool for BashTool {
output_delta: None,
was_bare_echo: false,
};
bash.output_for_prompt = format_default_prompt(&bash);
// Gate the no-op end-turn reminder on the same switch as every other
// system reminder (absent resource => enabled, mirroring
// `finalize_output`), so toolsets with `system_reminders_enabled=false`
// don't receive it.
let append_noop_reminder = resources
.lock()
.await
.get::<crate::types::resources::SystemRemindersEnabled>()
.is_none_or(|e| e.0);
bash.output_for_prompt = format_default_prompt(&bash, append_noop_reminder);
// Bare `echo "<msg>"` usage (common model anti-pattern for "just output something").
// We tag it for statistics (grok_build backend) and can surface an educational
@ -3416,7 +3429,7 @@ mod tests {
output_delta: None,
was_bare_echo: false,
};
bash.output_for_prompt = format_default_prompt(&bash);
bash.output_for_prompt = format_default_prompt(&bash, /* append_noop_reminder */ true);
bash
}
@ -3469,7 +3482,7 @@ mod tests {
let mut bash = make_bash_output(-1, "partial\n");
bash.signal = Some("timeout".to_string());
bash.timed_out = true;
bash.output_for_prompt = format_default_prompt(&bash);
bash.output_for_prompt = format_default_prompt(&bash, /* append_noop_reminder */ true);
// Synthetic kill reasons render as `exit: killed (reason)` — no
// redundant `[signal=…]` / `[timeout]` annotation.
assert!(
@ -3514,7 +3527,8 @@ mod tests {
for reason in ["timeout", "max_runtime", "cancelled", "killed", "signal 15"] {
let mut bash = make_bash_output(-1, "partial\n");
bash.signal = Some(reason.to_string());
bash.output_for_prompt = format_default_prompt(&bash);
bash.output_for_prompt =
format_default_prompt(&bash, /* append_noop_reminder */ true);
let expected = format!("exit: killed ({})", reason);
assert!(
bash.output_for_prompt.starts_with(&expected),
@ -3534,7 +3548,7 @@ mod tests {
let mut oom = make_bash_output(137, "killed\n");
oom.signal = Some("oom".to_string());
oom.output_for_prompt = format_default_prompt(&oom);
oom.output_for_prompt = format_default_prompt(&oom, /* append_noop_reminder */ true);
assert!(oom.output_for_prompt.starts_with("exit: 137 [signal=oom]"));
}
@ -3544,7 +3558,7 @@ mod tests {
bash.signal = Some("backgrounded".to_string());
bash.output_file = "/tmp/bg.log".to_string();
bash.total_bytes = 10000;
bash.output_for_prompt = format_default_prompt(&bash);
bash.output_for_prompt = format_default_prompt(&bash, /* append_noop_reminder */ true);
assert!(
bash.output_for_prompt
.starts_with("[Command moved to background]")
@ -3587,7 +3601,10 @@ mod tests {
"printf hi",
"printf 'done\\n'",
] {
let prompt = format_default_prompt(&bash_output_with_command(cmd, ""));
let prompt = format_default_prompt(
&bash_output_with_command(cmd, ""),
/* append_noop_reminder */ true,
);
assert!(
prompt.contains(NOOP_END_TURN_REMINDER),
"no-op command {cmd:?} should append the end-turn reminder, got: {prompt:?}"
@ -3595,6 +3612,23 @@ mod tests {
}
}
/// With `append_noop_reminder = false` (session `system_reminders_enabled=false`),
/// the no-op end-turn reminder is suppressed even for no-op commands. Mirrors
/// gating the reminder on the shared `SystemRemindersEnabled` switch.
#[test]
fn default_prompt_noop_reminder_suppressed_when_disabled() {
for cmd in ["true", ":", "", "echo ok", "printf hi"] {
let prompt = format_default_prompt(
&bash_output_with_command(cmd, ""),
/* append_noop_reminder */ false,
);
assert!(
!prompt.contains("<system-reminder>"),
"no-op command {cmd:?} must not append the reminder when disabled, got: {prompt:?}"
);
}
}
#[test]
fn default_prompt_normal_command_has_no_end_turn_reminder() {
for cmd in [
@ -3609,7 +3643,10 @@ mod tests {
"echo hi; ls",
"printf '%s' \"$x\"",
] {
let prompt = format_default_prompt(&bash_output_with_command(cmd, "hi\n"));
let prompt = format_default_prompt(
&bash_output_with_command(cmd, "hi\n"),
/* append_noop_reminder */ true,
);
assert!(
!prompt.contains("<system-reminder>"),
"normal command {cmd:?} must not append the end-turn reminder, got: {prompt:?}"

View file

@ -299,12 +299,26 @@ pub enum ImageGenConfig {
},
}
/// Session-id header attached to imagine API requests; matches the header
/// chat requests already carry.
pub const SESSION_ID_HEADER: &str = "x-grok-session-id";
impl ImageGenConfig {
/// Credentials present — required to construct any of the clients.
pub fn has_credentials(&self) -> bool {
matches!(self, Self::Enabled { .. })
}
/// Stamp [`SESSION_ID_HEADER`] onto `extra_headers`. A caller-provided
/// value is never overwritten. No-op when `Disabled`.
pub fn stamp_session_id_header(&mut self, session_id: &str) {
if let Self::Enabled { extra_headers, .. } = self {
extra_headers
.entry(SESSION_ID_HEADER.to_string())
.or_insert_with(|| session_id.to_string());
}
}
pub fn image_gen_enabled(&self) -> bool {
matches!(
self,
@ -506,6 +520,44 @@ mod tests {
assert!(!ImageGenConfig::Disabled.has_credentials());
}
#[test]
fn stamp_session_id_header_sets_and_preserves() {
let mk = |headers: indexmap::IndexMap<String, String>| ImageGenConfig::Enabled {
api_key: "k".into(),
base_url: "https://api.x.ai/v1".into(),
extra_headers: headers,
image_gen_enabled: true,
image_edit_enabled: true,
model_override: None,
edit_model_override: None,
tier_restricted: false,
};
let hdrs = |cfg: &ImageGenConfig| match cfg {
ImageGenConfig::Enabled { extra_headers, .. } => extra_headers.clone(),
_ => unreachable!(),
};
let mut cfg = mk(indexmap::IndexMap::new());
cfg.stamp_session_id_header("sess-123");
assert_eq!(
hdrs(&cfg).get(SESSION_ID_HEADER).map(String::as_str),
Some("sess-123")
);
let mut preset = indexmap::IndexMap::new();
preset.insert(SESSION_ID_HEADER.to_string(), "caller-set".to_string());
let mut cfg = mk(preset);
cfg.stamp_session_id_header("sess-123");
assert_eq!(
hdrs(&cfg).get(SESSION_ID_HEADER).map(String::as_str),
Some("caller-set")
);
let mut disabled = ImageGenConfig::Disabled;
disabled.stamp_session_id_header("sess-123");
assert!(!disabled.has_credentials());
}
#[test]
fn client_selects_model_from_override() {
let mk = |model_override: Option<&str>| ImageGenConfig::Enabled {

View file

@ -691,6 +691,16 @@ impl VideoGenConfig {
pub fn is_enabled(&self) -> bool {
matches!(self, Self::Enabled { .. })
}
/// Stamp [`super::image_gen::SESSION_ID_HEADER`] onto `extra_headers`.
/// A caller-provided value is never overwritten. No-op when `Disabled`.
pub fn stamp_session_id_header(&mut self, session_id: &str) {
if let Self::Enabled { extra_headers, .. } = self {
extra_headers
.entry(super::image_gen::SESSION_ID_HEADER.to_string())
.or_insert_with(|| session_id.to_string());
}
}
}
/// Prose returned to the model (as a normal, successful tool result) when a

View file

@ -265,8 +265,9 @@ mod tests {
// to_prompt_format() is a passthrough — it must NOT add another header.
let mut bash = make_bash(0, "hello world\n");
// Pre-bake DEFAULT (what BashTool::run() does)
bash.output_for_prompt =
crate::implementations::grok_build::bash::format_default_prompt(&bash);
bash.output_for_prompt = crate::implementations::grok_build::bash::format_default_prompt(
&bash, /* append_noop_reminder */ true,
);
assert!(bash.output_for_prompt.starts_with("exit: 0"));
// Concise post-processing (what BashConciseTool::run() does)

View file

@ -322,10 +322,17 @@ impl xai_tool_runtime::Tool for SearchTool {
} else {
"partial"
};
let note = if snapshot.is_ready {
None
} else {
let note = if !snapshot.is_ready {
Some("Some MCP servers are still connecting. Results may be incomplete.")
} else if snapshot.total_hidden_tools == 0 && result_groups.is_empty() {
// Ready but empty: help distinguish "MCP not set up / inheritance
// off" from a query that simply matched nothing. Wording is
// source-agnostic: search_tool runs in parent and subagent sessions.
Some(
"No MCP tools are available in this session. Connect MCP servers here, or if this is a subagent, check the agent's mcpInheritance.",
)
} else {
None
};
let response = serde_json::json!({
@ -420,6 +427,53 @@ mod tests {
);
}
#[tokio::test]
async fn search_tool_ready_empty_catalog_includes_guidance_note() {
let resources = crate::types::resources::Resources::default().into_shared();
resources
.lock()
.await
.insert(ToolIndex(std::sync::Arc::new(StaticToolIndex {
snapshot: SearchSnapshot {
results: vec![],
total_hidden_tools: 0,
is_ready: true,
},
})));
let mut ctx =
xai_tool_runtime::ToolCallContext::new(xai_tool_protocol::ToolCallId::new_v7());
ctx.extensions.insert(resources);
let output = SearchTool
.run(
ctx,
SearchToolInput {
query: "confluence".into(),
limit: Some(5),
},
)
.await
.unwrap();
let ToolOutput::SearchTool(output) = output else {
panic!("expected search tool output");
};
let json: serde_json::Value = serde_json::from_str(&output.content).unwrap();
assert_eq!(json["status"], "ready");
assert_eq!(json["total_hidden_tools"], 0);
assert!(json["results"].as_array().unwrap().is_empty());
let note = json["note"]
.as_str()
.expect("empty ready catalog should set note");
assert!(
note.contains("Connect MCP servers") && note.contains("mcpInheritance"),
"expected source-agnostic guidance about connecting servers / mcpInheritance, got: {note}"
);
assert!(
!note.contains("parent session"),
"must not assume a parent session (tool is shared with top-level sessions), got: {note}"
);
}
// -- truncate_description tests --
#[test]

View file

@ -983,7 +983,7 @@ impl ToolRegistryBuilder {
resources.insert(crate::types::resources::Cwd(cwd.clone()));
resources.insert(crate::types::resources::SessionFolder(ctx.session_folder));
resources.insert(crate::types::resources::SessionEnv(ctx.session_env));
if let Some(owner_session_id) = ctx.owner_session_id {
if let Some(owner_session_id) = ctx.owner_session_id.clone() {
resources.insert(crate::types::resources::OwnerSessionId(owner_session_id));
}
if let Some(subagent) = ctx.subagent {
@ -1022,9 +1022,15 @@ impl ToolRegistryBuilder {
if let Some(lsp) = ctx.lsp {
resources.insert(lsp);
}
if ctx.image_gen_config.has_credentials() {
let mut image_gen_config = ctx.image_gen_config;
let mut video_gen_config = ctx.video_gen_config;
if let Some(session_id) = &ctx.owner_session_id {
image_gen_config.stamp_session_id_header(session_id);
video_gen_config.stamp_session_id_header(session_id);
}
if image_gen_config.has_credentials() {
match crate::implementations::grok_build::image_gen::ImageGenClient::new(
&ctx.image_gen_config,
&image_gen_config,
ctx.api_key_provider.clone(),
) {
Ok(client) => {
@ -1036,9 +1042,9 @@ impl ToolRegistryBuilder {
}
}
}
if ctx.video_gen_config.is_enabled() {
if video_gen_config.is_enabled() {
match crate::implementations::grok_build::video_gen::VideoGenClient::new(
&ctx.video_gen_config,
&video_gen_config,
ctx.api_key_provider.clone(),
) {
Ok(client) => {