Skip to content

Commit 8273185

Browse files
authored
fix(tut): Detect actual goodbye subagent names (#483)
## Summary 🤖 Generated with [Nori](https://noriagentic.com/) - Ignore internal `agentId` / `agent_id` values when collecting goodbye-card subagent stats. - Preserve real subagent names from `subagent_type`, `agent_type`, and `agentType` in live TUI stats and ACP transcript fallback parsing. - Add regression coverage for Codex/Claude-style payloads where internal IDs appear alongside displayable subagent type fields. ## Test Plan - [x] `cargo test -p nori-tui` - [x] `cargo build -p mock-acp-agent` - [x] `cargo test -p nori-acp` - [x] `cargo build --bin nori` - [x] `cargo test -p tui-pty-e2e` - [x] Live tmux/bash verification with `nori --agent codex` - [x] Live tmux/bash regression with `nori --agent claude` Share Nori with your team: https://www.npmjs.com/package/nori-skillsets
1 parent 7137450 commit 8273185

2 files changed

Lines changed: 78 additions & 14 deletions

File tree

nori-rs/acp/src/transcript_discovery.rs

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -384,13 +384,7 @@ fn collect_subagents_from_value(value: &serde_json::Value, subagents: &mut Vec<S
384384
}
385385
}
386386
serde_json::Value::Object(map) => {
387-
for key in [
388-
"subagent_type",
389-
"agentType",
390-
"agent_type",
391-
"agentId",
392-
"agent_id",
393-
] {
387+
for key in ["subagent_type", "agentType", "agent_type"] {
394388
if let Some(subagent) = map.get(key).and_then(serde_json::Value::as_str) {
395389
let subagent = subagent.to_string();
396390
if !subagents.contains(&subagent) {
@@ -727,6 +721,51 @@ mod tests {
727721
);
728722
}
729723

724+
#[test]
725+
fn test_parse_transcript_subagents_ignores_internal_agent_ids() {
726+
let temp_dir = TempDir::new().unwrap();
727+
let transcript_file = temp_dir.path().join("session.jsonl");
728+
729+
{
730+
let mut f = fs::File::create(&transcript_file).unwrap();
731+
writeln!(
732+
f,
733+
r#"{{"type":"response_item","item":{{"type":"agent_spawned","agentId":"019e3cb4-f515-7d62-af6a-e0016ef364f3","agent_type":"nori-code-researcher"}}}}"#
734+
)
735+
.unwrap();
736+
writeln!(
737+
f,
738+
r#"{{"type":"response_item","item":{{"type":"agent_update","agent_id":"a74c49cc919c22c12","status":"completed"}}}}"#
739+
)
740+
.unwrap();
741+
}
742+
743+
assert_eq!(
744+
parse_transcript_subagents(&transcript_file),
745+
vec!["nori-code-researcher".to_string()]
746+
);
747+
}
748+
749+
#[test]
750+
fn test_parse_transcript_subagents_keeps_camel_case_agent_type() {
751+
let temp_dir = TempDir::new().unwrap();
752+
let transcript_file = temp_dir.path().join("session.jsonl");
753+
754+
{
755+
let mut f = fs::File::create(&transcript_file).unwrap();
756+
writeln!(
757+
f,
758+
r#"{{"type":"response_item","item":{{"type":"agent_spawned","agentId":"019e3cb4-f515-7d62-af6a-e0016ef364f3","agentType":"nori-code-researcher"}}}}"#
759+
)
760+
.unwrap();
761+
}
762+
763+
assert_eq!(
764+
parse_transcript_subagents(&transcript_file),
765+
vec!["nori-code-researcher".to_string()]
766+
);
767+
}
768+
730769
#[test]
731770
fn test_parse_claude_total_tokens() {
732771
let temp_dir = TempDir::new().unwrap();

nori-rs/tui/src/session_stats.rs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -270,13 +270,7 @@ fn extract_subagent_from_value(value: &serde_json::Value) -> Option<String> {
270270
| serde_json::Value::String(_) => None,
271271
serde_json::Value::Array(values) => values.iter().find_map(extract_subagent_from_value),
272272
serde_json::Value::Object(map) => {
273-
for key in [
274-
"subagent_type",
275-
"agentType",
276-
"agent_type",
277-
"agentId",
278-
"agent_id",
279-
] {
273+
for key in ["subagent_type", "agentType", "agent_type"] {
280274
if let Some(value) = map.get(key).and_then(serde_json::Value::as_str) {
281275
return Some(value.to_string());
282276
}
@@ -479,6 +473,37 @@ mod tests {
479473
assert_eq!(result, None);
480474
}
481475

476+
#[test]
477+
fn extract_subagent_ignores_internal_agent_id_fields() {
478+
let raw_input = json!({
479+
"agentId": "019e3cb4-f515-7d62-af6a-e0016ef364f3",
480+
"agent_id": "a74c49cc919c22c12",
481+
"message": "subagent lifecycle update"
482+
});
483+
let result = extract_subagent_from_raw_input(Some(&raw_input));
484+
assert_eq!(result, None);
485+
}
486+
487+
#[test]
488+
fn extract_subagent_prefers_type_over_internal_agent_id() {
489+
let raw_input = json!({
490+
"agent_type": "nori-code-researcher",
491+
"agentId": "019e3cb4-f515-7d62-af6a-e0016ef364f3"
492+
});
493+
let result = extract_subagent_from_raw_input(Some(&raw_input));
494+
assert_eq!(result, Some("nori-code-researcher".to_string()));
495+
}
496+
497+
#[test]
498+
fn extract_subagent_prefers_camel_case_type_over_internal_agent_id() {
499+
let raw_input = json!({
500+
"agentType": "nori-code-researcher",
501+
"agentId": "019e3cb4-f515-7d62-af6a-e0016ef364f3"
502+
});
503+
let result = extract_subagent_from_raw_input(Some(&raw_input));
504+
assert_eq!(result, Some("nori-code-researcher".to_string()));
505+
}
506+
482507
// =========================================================================
483508
// Tests for skill extraction from Read tool file paths
484509
// =========================================================================

0 commit comments

Comments
 (0)