the swarm controller streams every inter-agent message from the agents' terminal rows
This commit is contained in:
parent
87b1f94ac6
commit
c32906b893
11 changed files with 520 additions and 12 deletions
|
|
@ -15,7 +15,7 @@
|
|||
//! `mcp__hyperhive__send`'s row say, which tools get an expandable body)
|
||||
//! didn't change; only the shape it gets packed into did.
|
||||
|
||||
use crate::term_msg::{BodyFormat, ClassifyCtx, Level, TermMsg};
|
||||
use crate::term_msg::{BodyFormat, ClassifyCtx, Level, SEND_ICON, SEND_SUMMARY_PREFIX, TermMsg};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
/// Classify one raw claude stream-json line into zero or more terminal rows.
|
||||
|
|
@ -490,7 +490,7 @@ fn rich_tool_body(name: &str, input: &Value) -> Option<(String, &'static str)> {
|
|||
fn tool_icon(name: &str) -> &'static str {
|
||||
// Exact-match table first, then prefix/contains fallbacks.
|
||||
match name {
|
||||
"mcp__hyperhive__send" => "📤",
|
||||
"mcp__hyperhive__send" => SEND_ICON,
|
||||
"mcp__hyperhive__recv" => "📥",
|
||||
"mcp__hyperhive__remind" => "⏰",
|
||||
"mcp__hyperhive__set_status" => "🏷️",
|
||||
|
|
@ -610,9 +610,9 @@ fn fmt_hyperhive_message_tool(name: &str, short: &str, input: &Value) -> String
|
|||
let to = sv(input, "to");
|
||||
let lines = sv(input, "body").lines().count();
|
||||
if lines > 1 {
|
||||
format!("{short} → {to} · {lines}L")
|
||||
format!("{SEND_SUMMARY_PREFIX}{to} · {lines}L")
|
||||
} else {
|
||||
format!("{short} → {to}")
|
||||
format!("{SEND_SUMMARY_PREFIX}{to}")
|
||||
}
|
||||
}
|
||||
_ => fmt_args_generic(short, input),
|
||||
|
|
@ -1331,4 +1331,29 @@ mod tests {
|
|||
assert_eq!(fmt_room("!ÄÖÜ:server"), "!ÄÖÜ");
|
||||
assert_eq!(fmt_room("!ÄÖÜ"), "!ÄÖÜ");
|
||||
}
|
||||
|
||||
/// The send row the classifier builds is the shape `send_recipient`
|
||||
/// reads back, with and without the line-count suffix.
|
||||
#[test]
|
||||
fn a_send_row_names_its_recipient_through_the_shared_parser() {
|
||||
for (body, lines) in [("hi", false), ("one\ntwo\nthree", true)] {
|
||||
let row = one(&json!({
|
||||
"type": "assistant",
|
||||
"message": { "content": [{
|
||||
"type": "tool_use", "id": "s1", "name": "mcp__hyperhive__send",
|
||||
"input": { "to": "iris", "body": body },
|
||||
}]},
|
||||
}));
|
||||
assert_eq!(row.icon.as_deref(), Some(SEND_ICON));
|
||||
assert_eq!(row.summary.contains(" · 3L"), lines, "{}", row.summary);
|
||||
assert_eq!(crate::term_msg::send_recipient(&row.summary), Some("iris"));
|
||||
}
|
||||
assert_eq!(crate::term_msg::send_recipient("send* →"), None);
|
||||
assert_eq!(crate::term_msg::send_recipient("Read(x)"), None);
|
||||
// A suffix lookalike in the name is not stripped.
|
||||
assert_eq!(
|
||||
crate::term_msg::send_recipient("send* → a · xL"),
|
||||
Some("a · xL")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,6 +128,41 @@ pub fn iso8601_utc(unix_seconds: i64) -> String {
|
|||
.to_rfc3339_opts(SecondsFormat::Secs, true)
|
||||
}
|
||||
|
||||
/// Icon of the row an agent's `send` tool call makes.
|
||||
pub const SEND_ICON: &str = "📤";
|
||||
|
||||
/// What a `send` row's summary starts with; the recipient follows. A
|
||||
/// multi-line body appends ` · <N>L`, which [`send_recipient`] drops.
|
||||
pub const SEND_SUMMARY_PREFIX: &str = "send* → ";
|
||||
|
||||
/// Icon of the row a turn started by a message makes.
|
||||
pub const TURN_ICON: &str = "◆";
|
||||
|
||||
/// What a turn-start row's summary starts with; the sender follows.
|
||||
pub const TURN_SUMMARY_PREFIX: &str = "TURN ← ";
|
||||
|
||||
/// The recipient a `send` row's summary names, without the line-count suffix.
|
||||
#[must_use]
|
||||
pub fn send_recipient(summary: &str) -> Option<&str> {
|
||||
let rest = summary.strip_prefix(SEND_SUMMARY_PREFIX)?;
|
||||
Some(match rest.rsplit_once(" · ") {
|
||||
Some((to, lines))
|
||||
if lines
|
||||
.strip_suffix('L')
|
||||
.is_some_and(|n| !n.is_empty() && n.bytes().all(|b| b.is_ascii_digit())) =>
|
||||
{
|
||||
to
|
||||
}
|
||||
_ => rest,
|
||||
})
|
||||
}
|
||||
|
||||
/// The sender a turn-start row's summary names.
|
||||
#[must_use]
|
||||
pub fn turn_sender(summary: &str) -> Option<&str> {
|
||||
summary.strip_prefix(TURN_SUMMARY_PREFIX)
|
||||
}
|
||||
|
||||
/// Per-connection/per-request classification state. A live SSE stream keeps
|
||||
/// one of these alive for the connection's lifetime — `tool_use` id → name
|
||||
/// correlation, so a `tool_result` can tell it's answering a `recv` call and
|
||||
|
|
|
|||
Loading…
Reference in a new issue