use crate::app::Snapshot;
use crate::attention::{self, AttentionKind, RosterKey};
use crate::git_tree::{Agent, AgentState};
use crate::nav::{convs, ws_key};
use crate::ui_state::UiState;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QueueRow {
pub workspace: String,
pub agent: String,
pub display: String,
pub state: AgentState,
pub uncertain: bool,
pub signals: Vec<AttentionKind>,
pub preview: String,
pub age_secs: i64,
pub pending: usize,
pub held: Option<crate::control::hold::Held>,
pub failure: Option<String>,
pub flag: Option<crate::monitor::Flag>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Acknowledged {
pub workspace: String,
pub agent: String,
pub remaining: Vec<QueueRow>,
}
pub fn roster(snap: &Snapshot, ui: &UiState) -> Vec<RosterKey> {
let mut keyed: Vec<(PathBuf, String)> = snap
.workspaces
.iter()
.filter(|w| snap.trees.contains_key(&w.path))
.map(|w| (w.path.clone(), ws_key(&w.path)))
.collect();
keyed.sort_by(|a, b| a.1.cmp(&b.1));
let wss: Vec<(&str, &[Agent])> = keyed
.iter()
.filter_map(|(p, k)| snap.trees.get(p).map(|t| (k.as_str(), t.agents.as_slice())))
.collect();
let seen = |k, w: &str, a: &str, o: &str| ui.is_seen(k, w, a, o);
attention::roster_order(&wss, &seen)
}
pub fn queue(snap: &Snapshot, ui: &UiState, now_unix: i64) -> Vec<QueueRow> {
roster(snap, ui)
.into_iter()
.filter(|key| key.attention)
.filter_map(|key| row(snap, ui, &key, now_unix))
.collect()
}
fn row(snap: &Snapshot, ui: &UiState, key: &RosterKey, now_unix: i64) -> Option<QueueRow> {
let workspace = PathBuf::from(&key.ws);
let agents = &snap.trees.get(&workspace)?.agents;
let agent = agents.iter().find(|a| a.agent_id == key.agent_id)?;
let seen = |k, w: &str, a: &str, o: &str| ui.is_seen(k, w, a, o);
Some(QueueRow {
workspace: snap.ws_name(&workspace),
agent: agent.agent_id.clone(),
display: convs::member_title(agent),
state: agent.state,
uncertain: agent.state_uncertain,
signals: attention::attention(agent, agents, &key.ws, &seen).kinds(),
preview: convs::preview_of(agent),
age_secs: (now_unix - agent.last_action_unix).max(0),
pending: agent.pending.len(),
held: agent.held.clone(),
failure: agent.failure.as_deref().map(crate::git_tree::clause),
flag: agent.flagged.clone(),
})
}
pub fn mark_seen(snap: &Snapshot, ui: &mut UiState, ws: &Path, agent: &str) -> Result<(), String> {
let marks = snap
.trees
.get(ws)
.and_then(|t| t.agents.iter().find(|a| a.agent_id == agent))
.map(attention::evidence)
.ok_or_else(|| format!("no conversation {agent:?} in {:?}", snap.ws_name(ws)))?;
ui.record_seen(&ws_key(ws), agent, &marks);
Ok(())
}
#[cfg(test)]
mod tests;