Skip to content

Commit c4c4c6f

Browse files
committed
fix(parser): derive subagent_desc from skill prompt when parent tool call has no description
Skill-forked agents (e.g. spawned by /pir via Skill tool in forked-execution mode) have no Agent tool call in the parent session, so subagent_desc was never populated and the detail-item__summary showed blank for all agents. Add a fallback in convert_display_items: when fdi.subagent_desc is empty after linking a process, extract the skill name from proc.prompt via orphan_description_from_prompt. This covers Skill-forked orphans and any future pattern where the parent tool call carries no description field. Make orphan_description_from_prompt pub so convert.rs can import it.
1 parent 1766bde commit c4c4c6f

2 files changed

Lines changed: 47 additions & 2 deletions

File tree

src-tauri/src/convert.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::collections::{HashMap, HashSet};
55
use crate::parser::chunk::*;
66
use crate::parser::last_output::{find_last_output, LastOutput, LastOutputType};
77
use crate::parser::ongoing::OngoingChecker;
8-
use crate::parser::subagent::{ProcGraph, SubagentProcess};
8+
use crate::parser::subagent::{orphan_description_from_prompt, ProcGraph, SubagentProcess};
99
use crate::parser::taxonomy::ToolCategory;
1010
use crate::parser::team::TeamSnapshot;
1111

@@ -258,6 +258,11 @@ fn convert_display_items(
258258
fdi.subagent_ongoing = OngoingChecker::is_subagent_ongoing_deep(proc, graph);
259259
fdi.agent_id = proc.id.clone();
260260
fdi.subagent_prompt = proc.prompt.clone();
261+
// Fallback: derive description from the agent's own prompt when the
262+
// parent tool call carried no description (e.g. Skill-forked agents).
263+
if fdi.subagent_desc.is_empty() && !proc.prompt.is_empty() {
264+
fdi.subagent_desc = orphan_description_from_prompt(&proc.prompt);
265+
}
261266
if !proc.teammate_color.is_empty() {
262267
fdi.team_color = proc.teammate_color.clone();
263268
}
@@ -598,6 +603,46 @@ mod tests {
598603
assert_eq!(result[0].agent_id, "agent-abc");
599604
}
600605

606+
#[test]
607+
fn subagent_desc_derived_from_skill_prompt_when_empty() {
608+
use crate::parser::chunk::{DisplayItem, DisplayItemType};
609+
use crate::parser::subagent::SubagentProcess;
610+
611+
let tool_id = "orphan-ae9be0e043273ded1".to_string();
612+
// Orphan item with empty subagent_desc (as inject_orphan_subagents may produce
613+
// in edge cases, e.g. Skill-forked agents with no skill_progress linking).
614+
let items = vec![DisplayItem {
615+
item_type: DisplayItemType::Subagent,
616+
tool_id: tool_id.clone(),
617+
tool_name: "Agent".to_string(),
618+
subagent_type: "general-purpose".to_string(),
619+
subagent_desc: String::new(),
620+
..Default::default()
621+
}];
622+
623+
let proc = SubagentProcess {
624+
id: "ae9be0e043273ded1".to_string(),
625+
parent_task_id: tool_id,
626+
prompt:
627+
"Base directory for this skill: /Users/yang/.claude/skills/rollbar-reader\n\n# Rollbar Reader"
628+
.to_string(),
629+
..Default::default()
630+
};
631+
let subagents = vec![proc];
632+
let graph = ProcGraph::new(&subagents);
633+
let color_map = std::collections::HashMap::new();
634+
let mut pool_idx = 0;
635+
636+
let result =
637+
convert_display_items(&items, &graph, &color_map, &mut pool_idx, &HashSet::new());
638+
639+
assert_eq!(result.len(), 1);
640+
assert_eq!(
641+
result[0].subagent_desc, "rollbar-reader",
642+
"skill name must be derived from prompt when subagent_desc is empty"
643+
);
644+
}
645+
601646
// ---- cycle detection test ----
602647

603648
#[test]

src-tauri/src/parser/subagent.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,7 @@ pub fn inject_orphan_subagents(chunks: &mut Vec<Chunk>, processes: &mut [Subagen
769769
/// Derive a human-readable description for an orphan subagent from its prompt.
770770
/// Skill-based subagents typically start with "Base directory for this skill: /…/skill-name\n".
771771
/// Falls back to the first 80 characters of the prompt.
772-
fn orphan_description_from_prompt(prompt: &str) -> String {
772+
pub fn orphan_description_from_prompt(prompt: &str) -> String {
773773
if prompt.is_empty() {
774774
return String::new();
775775
}

0 commit comments

Comments
 (0)