From b62652c01ba019fd061a3f9983f4611538dd69ee Mon Sep 17 00:00:00 2001 From: iris Date: Sun, 19 Jul 2026 18:16:46 +0200 Subject: [PATCH] feat(#2196): stamp _body on mcp__bash__run, drop tool-specific JS branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend now stamps `_body` (full `$ `) alongside `_summary` (first line) and `_category: "rich"` for mcp__bash__run tool_use entries. The frontend drops the mcp__bash__run-specific branch in renderRichToolUse and uses a generic `if (c._body)` path instead โ€” api.details() with the backend-computed summary and body, no JS knowledge of the tool name. --- frontend/packages/agent/src/app.js | 13 +++++-------- hive-agent/src/stream_enrich.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/frontend/packages/agent/src/app.js b/frontend/packages/agent/src/app.js index 7d2b424e..7550c321 100644 --- a/frontend/packages/agent/src/app.js +++ b/frontend/packages/agent/src/app.js @@ -1559,14 +1559,11 @@ window.marked = marked; 'answer #' + id + (lines > 1 ? ` ยท ${lines}L` : ''), a, icon); } - // Bash task runner โ€” show full command in an expandable pre block so - // multi-line scripts are readable. Summary uses the first line so the - // row is identifiable without expanding. - if (name === 'mcp__bash__run') { - const cmd = String(input.cmd || ''); - const firstLine = cmd.split('\n')[0]; - const summary = 'run* $ ' + trim(firstLine.trim(), 72); - return api.details('tool-use', summary, '$ ' + cmd, icon); + // Generic plain-text body: the backend stamps `_body` for tools whose + // expandable content is plain text (e.g. mcp__bash__run full command). + // Render as a collapsible details row without any tool-specific logic. + if (c._body) { + return api.details('tool-use', c._summary || name || '?', c._body, icon); } return null; } diff --git a/hive-agent/src/stream_enrich.rs b/hive-agent/src/stream_enrich.rs index 1fd37ac3..c94ab281 100644 --- a/hive-agent/src/stream_enrich.rs +++ b/hive-agent/src/stream_enrich.rs @@ -230,6 +230,7 @@ fn enrich_tool_use_entry(entry: &mut Value) { let icon = tool_icon(&name); let summary = fmt_tool_use(&name, &input); let rich = is_rich_tool(&name); + let body = rich_tool_body(&name, &input); let Some(obj) = entry.as_object_mut() else { return; }; @@ -238,6 +239,9 @@ fn enrich_tool_use_entry(entry: &mut Value) { if rich { obj.insert("_category".to_owned(), json!("rich")); } + if let Some(b) = body { + obj.insert("_body".to_owned(), json!(b)); + } } // --------------------------------------------------------------------------- @@ -260,6 +264,29 @@ fn is_rich_tool(name: &str) -> bool { ) } +/// Pre-compute the expandable body text for tools that have one. +/// +/// Returns `Some(body)` when the tool has a meaningful multi-line body that +/// the frontend can display in a `
` block without tool-specific JS. +/// `None` for tools whose body is computed client-side (diffs, markdown) or +/// that have no body at all. +fn rich_tool_body(name: &str, input: &Value) -> Option { + match name { + // Full bash command โ€” first line is already in `_summary`; the full + // `cmd` (prefixed with `$ `) is the body so multi-line scripts are + // readable on expand. + "mcp__bash__run" => { + let cmd = input.get("cmd").and_then(Value::as_str).unwrap_or(""); + if cmd.is_empty() { + None + } else { + Some(format!("$ {cmd}")) + } + } + _ => None, + } +} + fn tool_icon(name: &str) -> &'static str { // Exact-match table first, then prefix/contains fallbacks. match name {