use crate::reply::queue::{Held, QueueRow};
use crate::reply::roster::Workspaces;
use crate::reply::stream::window::Window;
use crate::reply::stream::{Delta, Stream};
use super::parts::{age, brief, clause, line, line_over, listing, narrated, tally, things, when};
mod convs;
pub(super) use convs::conversations;
pub(super) fn workspaces(roster: &Workspaces) -> String {
let rows = roster
.rows
.iter()
.map(|row| {
line(vec![
Some(row.workspace.clone()),
Some(row.kind.label()),
things(row.agents, "conversation"),
tally(row.attention, "waiting"),
when(row.running, "running"),
clause("pinned", row.pinned.map(|n| n.to_string()).as_deref()),
])
})
.collect();
let head = line(vec![
Some("workspaces".to_owned()),
clause("(stale:", roster.stale.as_deref()).map(|said| format!("{said})")),
clause("(growing:", roster.growth.as_deref()).map(|said| format!("{said})")),
]);
listing(
&head,
rows,
"none yet — `lernie start home \"<goal>\"` founds the first",
)
}
pub(super) fn attention(rows: &[QueueRow]) -> String {
let painted = rows
.iter()
.map(|row| {
let head = line(vec![
Some(format!("{}/{}", row.workspace, row.display)),
Some(row.state.label()),
when(row.uncertain, "(uncertain)"),
Some(age(row.age_secs)),
tally(row.pending, "queued"),
when(!row.signals.is_empty(), &row.signals.join(", ")),
clause("flagged:", row.flag.as_ref().map(|f| f.reason.as_str())),
clause("failed:", row.failure.as_deref().map(brief).as_deref()),
]);
line_over(&head, row.held.as_ref().map(parked))
})
.collect();
listing("waiting on you", painted, "nothing is waiting on you")
}
pub(super) fn parked(held: &Held) -> String {
format!("held: {} ({}) — {}", held.tool, held.tool_use, held.reason)
}
pub(super) fn follow(stream: &Stream) -> String {
let landed = line_over(
&line(vec![
clause("(thinking)", stream.thinking.as_deref()),
stream.text.clone(),
]),
Some(narrated(
&stream
.tools
.iter()
.map(running)
.collect::<Vec<String>>()
.join("\n"),
)),
);
if !landed.is_empty() {
return landed;
}
narrated(&line(vec![
Some("…".to_owned()),
stream.last_delta.as_ref().map(Delta::label),
]))
}
pub(crate) fn at_rest(agent: &str, state: &str, secs: i64) -> String {
narrated(&format!(
"{agent} is at rest ({state}) after {} — nothing more will arrive \
until it is nudged or messaged",
age(secs)
))
}
pub(crate) fn held_at(agent: &str, workspace: &str, held: &Held, secs: i64) -> String {
narrated(&format!(
"{agent} is holding {} ({}) for your answer after {} — {}; release or decline it \
with `lernie answer {workspace} {agent} pass|refuse` (a third word widens the \
answer to every call like it)",
held.tool,
held.tool_use,
age(secs),
held.reason
))
}
pub(crate) fn waiting_on_mail(agent: &str, state: &str, deposits: usize) -> String {
narrated(&format!(
"{agent} is at rest ({state}) with {} not yet taken — holding until a driver takes \
it; Ctrl-C to stop watching",
things(u64::try_from(deposits).unwrap_or(u64::MAX), "deposit").unwrap_or_default()
))
}
fn running(call: &Window) -> String {
line(vec![
Some(status(call)),
Some(call.tool.clone().unwrap_or_else(|| call.tool_use.clone())),
call.input.as_deref().map(brief),
call.held.as_deref().map(brief),
])
}
fn status(call: &Window) -> String {
if let Some(code) = call.exit_code {
return format!("exit {code}");
}
if call.held.is_some() {
return HELD.to_owned();
}
RUNNING.to_owned()
}
const RUNNING: &str = "running";
const HELD: &str = "held";
pub(super) fn tail(thinking: &str, text: &str) -> String {
line(vec![
when(
!thinking.is_empty(),
&line_over("thinking", Some(thinking.to_owned())),
),
when(!text.is_empty(), text),
])
}