mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-10 11:23:32 +08:00
feat: jump ecc2 approval queue targets
This commit is contained in:
@@ -73,6 +73,7 @@ pub async fn run(db: StateStore, cfg: Config) -> Result<()> {
|
||||
(_, KeyCode::Char('b')) => dashboard.rebalance_selected_team().await,
|
||||
(_, KeyCode::Char('B')) => dashboard.rebalance_all_teams().await,
|
||||
(_, KeyCode::Char('i')) => dashboard.drain_inbox_selected().await,
|
||||
(_, KeyCode::Char('I')) => dashboard.focus_next_approval_target(),
|
||||
(_, KeyCode::Char('g')) => dashboard.auto_dispatch_backlog().await,
|
||||
(_, KeyCode::Char('G')) => dashboard.coordinate_backlog().await,
|
||||
(_, KeyCode::Char('v')) => dashboard.toggle_output_mode(),
|
||||
|
||||
@@ -721,7 +721,7 @@ impl Dashboard {
|
||||
|
||||
fn render_status_bar(&self, frame: &mut Frame, area: Rect) {
|
||||
let base_text = format!(
|
||||
" [n]ew session natural spawn [N] [a]ssign re[b]alance global re[B]alance dra[i]n inbox [g]lobal dispatch coordinate [G]lobal [v]iew diff conflict proto[c]ol cont[e]nt filter time [f]ilter search scope [A] agent filter [o] [m]erge merge ready [M] auto-worktree [t] auto-merge [w] toggle [p]olicy [,/.] dispatch limit [s]top [u]resume [x]cleanup prune inactive [X] [d]elete [r]efresh [Tab] switch pane [j/k] scroll delegate [ or ] [Enter] open [+/-] resize [l]ayout {} [T]heme {} [?] help [q]uit ",
|
||||
" [n]ew session natural spawn [N] [a]ssign re[b]alance global re[B]alance dra[i]n inbox approval jump [I] [g]lobal dispatch coordinate [G]lobal [v]iew diff conflict proto[c]ol cont[e]nt filter time [f]ilter search scope [A] agent filter [o] [m]erge merge ready [M] auto-worktree [t] auto-merge [w] toggle [p]olicy [,/.] dispatch limit [s]top [u]resume [x]cleanup prune inactive [X] [d]elete [r]efresh [Tab] switch pane [j/k] scroll delegate [ or ] [Enter] open [+/-] resize [l]ayout {} [T]heme {} [?] help [q]uit ",
|
||||
self.layout_label(),
|
||||
self.theme_label()
|
||||
);
|
||||
@@ -802,6 +802,7 @@ impl Dashboard {
|
||||
" b Rebalance backed-up delegate handoff backlog for selected lead",
|
||||
" B Rebalance backed-up delegate handoff backlog across lead teams",
|
||||
" i Drain unread task handoffs from selected lead",
|
||||
" I Jump to the next unread approval/conflict target session",
|
||||
" g Auto-dispatch unread handoffs across lead sessions",
|
||||
" G Dispatch then rebalance backlog across lead teams",
|
||||
" v Toggle selected worktree diff in output pane",
|
||||
@@ -1178,6 +1179,28 @@ impl Dashboard {
|
||||
));
|
||||
}
|
||||
|
||||
pub fn focus_next_approval_target(&mut self) {
|
||||
self.sync_approval_queue();
|
||||
let Some(target_session_id) = self.next_approval_target_session_id() else {
|
||||
self.set_operator_note("approval queue clear".to_string());
|
||||
return;
|
||||
};
|
||||
|
||||
self.sync_selection_by_id(Some(&target_session_id));
|
||||
self.reset_output_view();
|
||||
self.reset_metrics_view();
|
||||
self.sync_selected_output();
|
||||
self.sync_selected_diff();
|
||||
self.unread_message_counts = self.db.unread_message_counts().unwrap_or_default();
|
||||
self.sync_selected_messages();
|
||||
self.sync_selected_lineage();
|
||||
self.refresh_logs();
|
||||
self.set_operator_note(format!(
|
||||
"focused approval target {}",
|
||||
format_session_id(&target_session_id)
|
||||
));
|
||||
}
|
||||
|
||||
pub async fn new_session(&mut self) {
|
||||
if self.active_session_count() >= self.cfg.max_parallel_sessions {
|
||||
tracing::warn!(
|
||||
@@ -2876,6 +2899,44 @@ impl Dashboard {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn next_approval_target_session_id(&self) -> Option<String> {
|
||||
let pending_items: usize = self.approval_queue_counts.values().sum();
|
||||
if pending_items == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let active_session_ids: HashSet<_> =
|
||||
self.sessions.iter().map(|session| &session.id).collect();
|
||||
let queue = self.db.unread_approval_queue(pending_items).ok()?;
|
||||
let mut seen = HashSet::new();
|
||||
let ordered_targets = queue
|
||||
.into_iter()
|
||||
.filter_map(|message| {
|
||||
if active_session_ids.contains(&message.to_session)
|
||||
&& seen.insert(message.to_session.clone())
|
||||
{
|
||||
Some(message.to_session)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if ordered_targets.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let current_session_id = self.selected_session_id();
|
||||
current_session_id
|
||||
.and_then(|session_id| {
|
||||
ordered_targets
|
||||
.iter()
|
||||
.position(|target_session_id| target_session_id == session_id)
|
||||
.map(|index| ordered_targets[(index + 1) % ordered_targets.len()].clone())
|
||||
})
|
||||
.or_else(|| ordered_targets.first().cloned())
|
||||
}
|
||||
|
||||
fn sync_output_scroll(&mut self, viewport_height: usize) {
|
||||
self.last_output_height = viewport_height.max(1);
|
||||
let max_scroll = self.max_output_scroll();
|
||||
@@ -4633,6 +4694,158 @@ mod tests {
|
||||
assert!(dashboard.approval_queue_preview.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn focus_next_approval_target_selects_oldest_unread_target() {
|
||||
let sessions = vec![
|
||||
sample_session(
|
||||
"lead-12345678",
|
||||
"planner",
|
||||
SessionState::Running,
|
||||
Some("ecc/lead"),
|
||||
512,
|
||||
42,
|
||||
),
|
||||
sample_session(
|
||||
"worker-a",
|
||||
"reviewer",
|
||||
SessionState::Idle,
|
||||
Some("ecc/worker-a"),
|
||||
64,
|
||||
5,
|
||||
),
|
||||
sample_session(
|
||||
"worker-b",
|
||||
"reviewer",
|
||||
SessionState::Idle,
|
||||
Some("ecc/worker-b"),
|
||||
64,
|
||||
5,
|
||||
),
|
||||
];
|
||||
let mut dashboard = test_dashboard(sessions, 0);
|
||||
for session in &dashboard.sessions {
|
||||
dashboard.db.insert_session(session).unwrap();
|
||||
}
|
||||
dashboard
|
||||
.db
|
||||
.send_message(
|
||||
"lead-12345678",
|
||||
"worker-b",
|
||||
"{\"question\":\"Need approval on B\"}",
|
||||
"query",
|
||||
)
|
||||
.unwrap();
|
||||
dashboard
|
||||
.db
|
||||
.send_message(
|
||||
"lead-12345678",
|
||||
"worker-a",
|
||||
"{\"question\":\"Need approval on A\"}",
|
||||
"query",
|
||||
)
|
||||
.unwrap();
|
||||
dashboard.sync_approval_queue();
|
||||
|
||||
dashboard.focus_next_approval_target();
|
||||
|
||||
assert_eq!(dashboard.selected_session_id(), Some("worker-b"));
|
||||
assert_eq!(
|
||||
dashboard.operator_note.as_deref(),
|
||||
Some("focused approval target worker-b")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn focus_next_approval_target_cycles_distinct_targets() {
|
||||
let sessions = vec![
|
||||
sample_session(
|
||||
"lead-12345678",
|
||||
"planner",
|
||||
SessionState::Running,
|
||||
Some("ecc/lead"),
|
||||
512,
|
||||
42,
|
||||
),
|
||||
sample_session(
|
||||
"worker-a",
|
||||
"reviewer",
|
||||
SessionState::Idle,
|
||||
Some("ecc/worker-a"),
|
||||
64,
|
||||
5,
|
||||
),
|
||||
sample_session(
|
||||
"worker-b",
|
||||
"reviewer",
|
||||
SessionState::Idle,
|
||||
Some("ecc/worker-b"),
|
||||
64,
|
||||
5,
|
||||
),
|
||||
];
|
||||
let mut dashboard = test_dashboard(sessions, 1);
|
||||
for session in &dashboard.sessions {
|
||||
dashboard.db.insert_session(session).unwrap();
|
||||
}
|
||||
dashboard
|
||||
.db
|
||||
.send_message(
|
||||
"lead-12345678",
|
||||
"worker-a",
|
||||
"{\"question\":\"Need approval on A\"}",
|
||||
"query",
|
||||
)
|
||||
.unwrap();
|
||||
dashboard
|
||||
.db
|
||||
.send_message(
|
||||
"lead-12345678",
|
||||
"worker-a",
|
||||
"{\"question\":\"Need another approval on A\"}",
|
||||
"conflict",
|
||||
)
|
||||
.unwrap();
|
||||
dashboard
|
||||
.db
|
||||
.send_message(
|
||||
"lead-12345678",
|
||||
"worker-b",
|
||||
"{\"question\":\"Need approval on B\"}",
|
||||
"query",
|
||||
)
|
||||
.unwrap();
|
||||
dashboard.sync_approval_queue();
|
||||
|
||||
dashboard.focus_next_approval_target();
|
||||
|
||||
assert_eq!(dashboard.selected_session_id(), Some("worker-b"));
|
||||
assert_eq!(dashboard.approval_queue_counts.get("worker-a"), Some(&2));
|
||||
assert_eq!(dashboard.approval_queue_counts.get("worker-b"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn focus_next_approval_target_reports_clear_queue() {
|
||||
let mut dashboard = test_dashboard(
|
||||
vec![sample_session(
|
||||
"lead-12345678",
|
||||
"planner",
|
||||
SessionState::Running,
|
||||
Some("ecc/lead"),
|
||||
512,
|
||||
42,
|
||||
)],
|
||||
0,
|
||||
);
|
||||
|
||||
dashboard.focus_next_approval_target();
|
||||
|
||||
assert_eq!(dashboard.selected_session_id(), Some("lead-12345678"));
|
||||
assert_eq!(
|
||||
dashboard.operator_note.as_deref(),
|
||||
Some("approval queue clear")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_session_metrics_text_includes_worktree_output_and_attention_queue() {
|
||||
let mut dashboard = test_dashboard(
|
||||
|
||||
Reference in New Issue
Block a user