mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-10 19:33:37 +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_selected_team().await,
|
||||||
(_, KeyCode::Char('B')) => dashboard.rebalance_all_teams().await,
|
(_, KeyCode::Char('B')) => dashboard.rebalance_all_teams().await,
|
||||||
(_, KeyCode::Char('i')) => dashboard.drain_inbox_selected().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.auto_dispatch_backlog().await,
|
||||||
(_, KeyCode::Char('G')) => dashboard.coordinate_backlog().await,
|
(_, KeyCode::Char('G')) => dashboard.coordinate_backlog().await,
|
||||||
(_, KeyCode::Char('v')) => dashboard.toggle_output_mode(),
|
(_, KeyCode::Char('v')) => dashboard.toggle_output_mode(),
|
||||||
|
|||||||
@@ -721,7 +721,7 @@ impl Dashboard {
|
|||||||
|
|
||||||
fn render_status_bar(&self, frame: &mut Frame, area: Rect) {
|
fn render_status_bar(&self, frame: &mut Frame, area: Rect) {
|
||||||
let base_text = format!(
|
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.layout_label(),
|
||||||
self.theme_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 for selected lead",
|
||||||
" B Rebalance backed-up delegate handoff backlog across lead teams",
|
" B Rebalance backed-up delegate handoff backlog across lead teams",
|
||||||
" i Drain unread task handoffs from selected lead",
|
" 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 Auto-dispatch unread handoffs across lead sessions",
|
||||||
" G Dispatch then rebalance backlog across lead teams",
|
" G Dispatch then rebalance backlog across lead teams",
|
||||||
" v Toggle selected worktree diff in output pane",
|
" 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) {
|
pub async fn new_session(&mut self) {
|
||||||
if self.active_session_count() >= self.cfg.max_parallel_sessions {
|
if self.active_session_count() >= self.cfg.max_parallel_sessions {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
@@ -2876,6 +2899,44 @@ impl Dashboard {
|
|||||||
.collect()
|
.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) {
|
fn sync_output_scroll(&mut self, viewport_height: usize) {
|
||||||
self.last_output_height = viewport_height.max(1);
|
self.last_output_height = viewport_height.max(1);
|
||||||
let max_scroll = self.max_output_scroll();
|
let max_scroll = self.max_output_scroll();
|
||||||
@@ -4633,6 +4694,158 @@ mod tests {
|
|||||||
assert!(dashboard.approval_queue_preview.is_empty());
|
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]
|
#[test]
|
||||||
fn selected_session_metrics_text_includes_worktree_output_and_attention_queue() {
|
fn selected_session_metrics_text_includes_worktree_output_and_attention_queue() {
|
||||||
let mut dashboard = test_dashboard(
|
let mut dashboard = test_dashboard(
|
||||||
|
|||||||
Reference in New Issue
Block a user