add per-agent and hive-wide skill invocation stats
This commit is contained in:
parent
2c11a437b4
commit
ef1554a1b2
4 changed files with 163 additions and 51 deletions
|
|
@ -643,6 +643,11 @@ impl Bus {
|
|||
/// pump on every parsed line. Cheap when the line isn't an
|
||||
/// assistant message — the field-check short-circuits.
|
||||
///
|
||||
/// A `Skill` invocation is one meta-tool (`name == "Skill"`) dispatching
|
||||
/// to whichever skill matched, so the bare tool name collapses every
|
||||
/// skill into one undifferentiated count. Special-cased (via
|
||||
/// [`breakdown_key`]) to key by `Skill:<skill>` instead.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the internal lock is poisoned.
|
||||
|
|
@ -665,9 +670,9 @@ impl Bus {
|
|||
let name = block
|
||||
.get("name")
|
||||
.and_then(|n| n.as_str())
|
||||
.unwrap_or("<unnamed>")
|
||||
.to_owned();
|
||||
*counts.entry(name).or_insert(0) += 1;
|
||||
.unwrap_or("<unnamed>");
|
||||
let key = breakdown_key(name, block.get("input"));
|
||||
*counts.entry(key).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -914,6 +919,24 @@ fn degraded_mcp_servers(
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// The `tool_call_breakdown_json` key for one `tool_use` block: the bare
|
||||
/// tool `name`, except a `Skill` invocation (one meta-tool dispatching to
|
||||
/// whichever skill matched) is keyed `Skill:<skill>` using the invocation's
|
||||
/// own `input.skill` field — the fully-qualified `plugin:skill-name` — so
|
||||
/// distinct skills don't collapse into one undifferentiated `"Skill"`
|
||||
/// count. Falls back to the bare `"Skill"` key if `input.skill` is ever
|
||||
/// missing/non-string, so a schema change degrades safely instead of losing
|
||||
/// the count. Pure so it's unit-testable without a live `Bus`.
|
||||
fn breakdown_key(name: &str, input: Option<&serde_json::Value>) -> String {
|
||||
if name != "Skill" {
|
||||
return name.to_owned();
|
||||
}
|
||||
input
|
||||
.and_then(|i| i.get("skill"))
|
||||
.and_then(|s| s.as_str())
|
||||
.map_or_else(|| name.to_owned(), |skill| format!("Skill:{skill}"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{BusEvent, LiveEvent, StoredEvent};
|
||||
|
|
@ -976,6 +999,30 @@ mod tests {
|
|||
assert!(super::degraded_mcp_servers(&configured, &reported).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn breakdown_key_keys_skill_by_input_field() {
|
||||
let input = serde_json::json!({"skill": "base:async-task-hygiene"});
|
||||
assert_eq!(
|
||||
super::breakdown_key("Skill", Some(&input)),
|
||||
"Skill:base:async-task-hygiene"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn breakdown_key_non_skill_tool_is_bare_name() {
|
||||
assert_eq!(super::breakdown_key("Read", None), "Read");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn breakdown_key_skill_missing_input_field_degrades_to_bare_name() {
|
||||
// Schema-drift fallback: an unexpected/missing `input.skill` still
|
||||
// counts the invocation, just undifferentiated, rather than losing
|
||||
// it entirely.
|
||||
assert_eq!(super::breakdown_key("Skill", None), "Skill");
|
||||
let input = serde_json::json!({"unexpected": "field"});
|
||||
assert_eq!(super::breakdown_key("Skill", Some(&input)), "Skill");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn degraded_mcp_servers_ignores_pending() {
|
||||
// `pending` is the CLI's normal init-event race for a stdio server
|
||||
|
|
|
|||
Loading…
Reference in a new issue