use crate::app::Snapshot;
use crate::nav::{self, convs::ConvBall, convs::ConvRow, ws_key};
use crate::projects::join;
use crate::ui_state::UiState;
use std::path::Path;
use super::dispatch::Deps;
use super::reply::Reply;
use super::{Query, config, help};
pub mod agent;
pub mod balls;
mod chrome;
mod confirm;
pub mod inspector;
pub mod queue;
pub use chrome::{workspace_stats, workspaces, ws_rows};
pub use confirm::{agent_confirmation_of, confirmation_of};
pub fn answer(query: &Query, deps: &Deps, ui: &UiState, now_unix: i64) -> Result<Reply, String> {
let snap = &deps.snapshot;
let ws: &std::path::Path = &match query.workspace() {
Some(name) => snap.ws_path(&name)?,
None => std::path::PathBuf::new(),
};
let agent: &str = &super::address::resolve_agent(snap, ws, query.agent())?;
Ok(match query {
Query::Workspaces => Reply::Workspaces(workspaces(snap, ui, now_unix)),
Query::Conversations { .. } => Reply::Conversations(conversations(snap, ui, ws, now_unix)),
Query::Balls => Reply::Balls(snap.join_rows.clone()),
Query::WorkspaceBalls { .. } => Reply::WorkspaceBalls(balls::ws_balls(snap, ui, ws)),
Query::Board => Reply::Board(crate::board::build(snap, ui, now_unix)),
Query::Attention => Reply::Attention(queue::queue(snap, ui, now_unix)),
Query::Help { verb } => Reply::Help(help::rows(verb.as_deref())),
Query::Search { text } => Reply::Search(crate::search::run(snap, text, &|| true)),
Query::WorkDiff { file, .. } => {
let entries = crate::opslog::tail(&deps.state_root, usize::MAX);
let xdg = deps.world.balls_layout();
let attempts = crate::workdiff::read(snap, ws, &entries, &xdg);
let patch = file
.as_ref()
.and_then(|f| crate::workdiff::patch(snap, &attempts, f));
Reply::WorkDiff { attempts, patch }
}
Query::Science { .. } => {
let entries = crate::opslog::tail(&deps.state_root, usize::MAX);
let xdg = deps.world.balls_layout();
Reply::Science(crate::science::project(
snap,
ws,
&entries,
&xdg,
&deps.balls_state_root,
))
}
Query::Transcript { .. } => Reply::Transcript(inspector::transcript(snap, ws, agent)),
Query::Follow { .. } => {
Reply::Follow(inspector::live_tail(snap, ws, agent).unwrap_or_default())
}
Query::Steps { .. } => Reply::Steps(inspector::steps(snap, ws, agent)),
Query::Step { seq, .. } => Reply::Step(crate::steps_view::detail(ws, agent, seq)),
Query::Files { path, at, .. } => {
let (view, preview) = inspector::files(ws, agent, path.as_deref(), at.as_deref());
Reply::Files { view, preview }
}
Query::Rail { .. } => {
let steps = inspector::steps(snap, ws, agent);
let tx = inspector::transcript(snap, ws, agent);
Reply::Rail(inspector::rail(snap, ws, agent, &steps, &tx))
}
Query::Inbox { .. } => Reply::Inbox(crate::inboxview::list_inbox(ws, agent)),
Query::Governing { at, .. } => {
return inspector::governing(snap, ws, agent, at.as_deref()).map(Reply::Governing);
}
Query::Agent { .. } => Reply::Agent(agent::agent(snap, ui, ws, agent, now_unix)),
Query::Ops { max } => {
let skip = snap.ops.len().saturating_sub(*max);
Reply::Ops(snap.ops.iter().skip(skip).cloned().collect())
}
Query::ReadConfig { file } => return config::read(deps, file),
Query::Marks { .. } => return Ok(config::read_marks(deps, ws)),
Query::Providers { .. } => config::providers(deps, ws),
Query::Lineages { .. } => return config::lineages(ws),
Query::Models { provider, .. } => return config::models(deps, ws, provider),
Query::Clients { workspace } => Reply::Clients(crate::registry::roster::roster(
&deps.state_root,
&deps.caller.presence,
workspace,
)),
Query::Invocations => return super::routing::invocations(deps),
Query::Capture { invocation } => return super::routing::capture(deps, invocation),
})
}
pub fn conversations(snap: &Snapshot, ui: &UiState, ws: &Path, now_unix: i64) -> Vec<ConvRow> {
let Some(tree) = snap.trees.get(ws) else {
return Vec::new();
};
let key = ws_key(ws);
let seen = |k, w: &str, a: &str, o: &str| ui.is_seen(k, w, a, o);
let ball = |id: &str| conv_ball(snap, id);
let checks = crate::monitor::row::of_rows(&snap.ops);
nav::convs::forest_rows(&tree.agents, &key, &seen, now_unix, &ball, &checks)
}
pub fn conv_ball(snap: &Snapshot, id: &str) -> ConvBall {
match snap.join_rows.iter().find(|r| r.ball_id == id) {
Some(r) => ConvBall {
id: id.to_owned(),
state: Some(r.state),
title: r.title.clone(),
badge: join::badge(r.state, r.claimant.as_deref()),
},
None => ConvBall {
id: id.to_owned(),
state: None,
title: None,
badge: None,
},
}
}
pub fn names_in(snap: &Snapshot, ws: &Path) -> Vec<String> {
snap.trees
.get(ws)
.into_iter()
.flat_map(|t| {
t.agents
.iter()
.filter_map(crate::git_tree::Agent::name_fact)
})
.collect()
}
#[cfg(test)]
mod tests;