mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-09 10:53:34 +08:00
feat: align ecc2 delegate backlog semantics
This commit is contained in:
@@ -42,7 +42,10 @@ pub fn get_status(db: &StateStore, id: &str) -> Result<SessionStatus> {
|
||||
|
||||
pub fn get_team_status(db: &StateStore, id: &str, depth: usize) -> Result<TeamStatus> {
|
||||
let root = resolve_session(db, id)?;
|
||||
let unread_counts = db.unread_message_counts()?;
|
||||
let handoff_backlog = db
|
||||
.unread_task_handoff_targets(db.list_sessions()?.len().max(1))?
|
||||
.into_iter()
|
||||
.collect();
|
||||
let mut visited = HashSet::new();
|
||||
visited.insert(root.id.clone());
|
||||
|
||||
@@ -52,14 +55,14 @@ pub fn get_team_status(db: &StateStore, id: &str, depth: usize) -> Result<TeamSt
|
||||
&root.id,
|
||||
depth,
|
||||
1,
|
||||
&unread_counts,
|
||||
&handoff_backlog,
|
||||
&mut visited,
|
||||
&mut descendants,
|
||||
)?;
|
||||
|
||||
Ok(TeamStatus {
|
||||
root,
|
||||
unread_messages: unread_counts,
|
||||
handoff_backlog,
|
||||
descendants,
|
||||
})
|
||||
}
|
||||
@@ -552,7 +555,7 @@ fn collect_delegation_descendants(
|
||||
session_id: &str,
|
||||
remaining_depth: usize,
|
||||
current_depth: usize,
|
||||
unread_counts: &std::collections::HashMap<String, usize>,
|
||||
handoff_backlog: &std::collections::HashMap<String, usize>,
|
||||
visited: &mut HashSet<String>,
|
||||
descendants: &mut Vec<DelegatedSessionSummary>,
|
||||
) -> Result<()> {
|
||||
@@ -571,7 +574,7 @@ fn collect_delegation_descendants(
|
||||
|
||||
descendants.push(DelegatedSessionSummary {
|
||||
depth: current_depth,
|
||||
unread_messages: unread_counts.get(&child_id).copied().unwrap_or(0),
|
||||
handoff_backlog: handoff_backlog.get(&child_id).copied().unwrap_or(0),
|
||||
session,
|
||||
});
|
||||
|
||||
@@ -580,7 +583,7 @@ fn collect_delegation_descendants(
|
||||
&child_id,
|
||||
remaining_depth.saturating_sub(1),
|
||||
current_depth + 1,
|
||||
unread_counts,
|
||||
handoff_backlog,
|
||||
visited,
|
||||
descendants,
|
||||
)?;
|
||||
@@ -843,14 +846,13 @@ fn summarize_backlog_pressure(
|
||||
agent_type: &str,
|
||||
targets: &[(String, usize)],
|
||||
) -> Result<BacklogPressureSummary> {
|
||||
let unread_counts = db.unread_message_counts()?;
|
||||
let mut summary = BacklogPressureSummary::default();
|
||||
|
||||
for (session_id, _) in targets {
|
||||
let delegates = direct_delegate_sessions(db, session_id, agent_type)?;
|
||||
let has_clear_idle_delegate = delegates.iter().any(|delegate| {
|
||||
delegate.state == SessionState::Idle
|
||||
&& unread_counts.get(&delegate.id).copied().unwrap_or(0) == 0
|
||||
&& db.unread_task_handoff_count(&delegate.id).unwrap_or(0) == 0
|
||||
});
|
||||
let has_capacity = delegates.len() < cfg.max_parallel_sessions;
|
||||
|
||||
@@ -1048,7 +1050,7 @@ pub struct SessionStatus {
|
||||
|
||||
pub struct TeamStatus {
|
||||
root: Session,
|
||||
unread_messages: std::collections::HashMap<String, usize>,
|
||||
handoff_backlog: std::collections::HashMap<String, usize>,
|
||||
descendants: Vec<DelegatedSessionSummary>,
|
||||
}
|
||||
|
||||
@@ -1112,7 +1114,7 @@ struct BacklogPressureSummary {
|
||||
|
||||
struct DelegatedSessionSummary {
|
||||
depth: usize,
|
||||
unread_messages: usize,
|
||||
handoff_backlog: usize,
|
||||
session: Session,
|
||||
}
|
||||
|
||||
@@ -1154,8 +1156,8 @@ impl fmt::Display for TeamStatus {
|
||||
writeln!(f, "Branch: {}", worktree.branch)?;
|
||||
}
|
||||
|
||||
let lead_unread = self.unread_messages.get(&self.root.id).copied().unwrap_or(0);
|
||||
writeln!(f, "Inbox: {}", lead_unread)?;
|
||||
let lead_handoff_backlog = self.handoff_backlog.get(&self.root.id).copied().unwrap_or(0);
|
||||
writeln!(f, "Backlog: {}", lead_handoff_backlog)?;
|
||||
|
||||
if self.descendants.is_empty() {
|
||||
return write!(f, "Board: no delegated sessions");
|
||||
@@ -1185,11 +1187,11 @@ impl fmt::Display for TeamStatus {
|
||||
for item in items {
|
||||
writeln!(
|
||||
f,
|
||||
" - {}{} [{}] | inbox {} | {}",
|
||||
" - {}{} [{}] | backlog {} handoff(s) | {}",
|
||||
" ".repeat(item.depth.saturating_sub(1)),
|
||||
item.session.id,
|
||||
item.session.agent_type,
|
||||
item.unread_messages,
|
||||
item.handoff_backlog,
|
||||
item.session.task
|
||||
)?;
|
||||
}
|
||||
@@ -2404,4 +2406,59 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn team_status_reports_handoff_backlog_not_generic_inbox_noise() -> Result<()> {
|
||||
let tempdir = TestDir::new("manager-team-status-backlog")?;
|
||||
let repo_root = tempdir.path().join("repo");
|
||||
init_git_repo(&repo_root)?;
|
||||
|
||||
let cfg = build_config(tempdir.path());
|
||||
let db = StateStore::open(&cfg.db_path)?;
|
||||
let now = Utc::now();
|
||||
|
||||
db.insert_session(&Session {
|
||||
id: "lead".to_string(),
|
||||
task: "lead task".to_string(),
|
||||
agent_type: "claude".to_string(),
|
||||
working_dir: repo_root.clone(),
|
||||
state: SessionState::Running,
|
||||
pid: Some(42),
|
||||
worktree: None,
|
||||
created_at: now - Duration::minutes(4),
|
||||
updated_at: now - Duration::minutes(4),
|
||||
metrics: SessionMetrics::default(),
|
||||
})?;
|
||||
db.insert_session(&Session {
|
||||
id: "worker".to_string(),
|
||||
task: "delegate task".to_string(),
|
||||
agent_type: "claude".to_string(),
|
||||
working_dir: repo_root,
|
||||
state: SessionState::Idle,
|
||||
pid: None,
|
||||
worktree: None,
|
||||
created_at: now - Duration::minutes(3),
|
||||
updated_at: now - Duration::minutes(3),
|
||||
metrics: SessionMetrics::default(),
|
||||
})?;
|
||||
|
||||
db.send_message("lead", "worker", "FYI status update", "info")?;
|
||||
db.send_message(
|
||||
"lead",
|
||||
"worker",
|
||||
"{\"task\":\"Delegated work\",\"context\":\"Delegated from lead\"}",
|
||||
"task_handoff",
|
||||
)?;
|
||||
let _ = db.mark_messages_read("worker")?;
|
||||
db.send_message("lead", "worker", "FYI reminder", "info")?;
|
||||
|
||||
let status = get_team_status(&db, "lead", 3)?;
|
||||
let rendered = format!("{status}");
|
||||
|
||||
assert!(rendered.contains("Backlog: 0"));
|
||||
assert!(rendered.contains("| backlog 0 handoff(s) |"));
|
||||
assert!(!rendered.contains("Inbox:"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ struct AggregateUsage {
|
||||
struct DelegatedChildSummary {
|
||||
session_id: String,
|
||||
state: SessionState,
|
||||
unread_messages: usize,
|
||||
handoff_backlog: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
@@ -1297,11 +1297,6 @@ impl Dashboard {
|
||||
match self.db.get_session(&child_id) {
|
||||
Ok(Some(session)) => {
|
||||
team.total += 1;
|
||||
let unread_messages = self
|
||||
.unread_message_counts
|
||||
.get(&child_id)
|
||||
.copied()
|
||||
.unwrap_or(0);
|
||||
let handoff_backlog = match self.db.unread_task_handoff_count(&child_id) {
|
||||
Ok(count) => count,
|
||||
Err(error) => {
|
||||
@@ -1323,12 +1318,12 @@ impl Dashboard {
|
||||
}
|
||||
|
||||
route_candidates.push(DelegatedChildSummary {
|
||||
unread_messages: handoff_backlog,
|
||||
handoff_backlog,
|
||||
state: state.clone(),
|
||||
session_id: child_id.clone(),
|
||||
});
|
||||
delegated.push(DelegatedChildSummary {
|
||||
unread_messages,
|
||||
handoff_backlog,
|
||||
state,
|
||||
session_id: child_id,
|
||||
});
|
||||
@@ -1365,7 +1360,7 @@ impl Dashboard {
|
||||
) -> Option<String> {
|
||||
if let Some(idle_clear) = delegates
|
||||
.iter()
|
||||
.filter(|delegate| delegate.state == SessionState::Idle && delegate.unread_messages == 0)
|
||||
.filter(|delegate| delegate.state == SessionState::Idle && delegate.handoff_backlog == 0)
|
||||
.min_by_key(|delegate| delegate.session_id.as_str())
|
||||
{
|
||||
return Some(format!(
|
||||
@@ -1381,24 +1376,24 @@ impl Dashboard {
|
||||
if let Some(idle_backed_up) = delegates
|
||||
.iter()
|
||||
.filter(|delegate| delegate.state == SessionState::Idle)
|
||||
.min_by_key(|delegate| (delegate.unread_messages, delegate.session_id.as_str()))
|
||||
.min_by_key(|delegate| (delegate.handoff_backlog, delegate.session_id.as_str()))
|
||||
{
|
||||
return Some(format!(
|
||||
"reuse idle {} with inbox {}",
|
||||
"reuse idle {} with backlog {}",
|
||||
format_session_id(&idle_backed_up.session_id),
|
||||
idle_backed_up.unread_messages
|
||||
idle_backed_up.handoff_backlog
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(active_delegate) = delegates
|
||||
.iter()
|
||||
.filter(|delegate| matches!(delegate.state, SessionState::Running | SessionState::Pending))
|
||||
.min_by_key(|delegate| (delegate.unread_messages, delegate.session_id.as_str()))
|
||||
.min_by_key(|delegate| (delegate.handoff_backlog, delegate.session_id.as_str()))
|
||||
{
|
||||
return Some(format!(
|
||||
"reuse active {} with inbox {}",
|
||||
"reuse active {} with backlog {}",
|
||||
format_session_id(&active_delegate.session_id),
|
||||
active_delegate.unread_messages
|
||||
active_delegate.handoff_backlog
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1588,10 +1583,10 @@ impl Dashboard {
|
||||
lines.push("Delegates".to_string());
|
||||
for child in &self.selected_child_sessions {
|
||||
lines.push(format!(
|
||||
"- {} [{}] | inbox {}",
|
||||
"- {} [{}] | backlog {}",
|
||||
format_session_id(&child.session_id),
|
||||
session_state_label(&child.state),
|
||||
child.unread_messages
|
||||
child.handoff_backlog
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -2422,7 +2417,7 @@ mod tests {
|
||||
Some("reuse idle idle-wor")
|
||||
);
|
||||
assert_eq!(dashboard.selected_child_sessions.len(), 1);
|
||||
assert_eq!(dashboard.selected_child_sessions[0].unread_messages, 1);
|
||||
assert_eq!(dashboard.selected_child_sessions[0].handoff_backlog, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user