use std::collections::BTreeSet;
use argonauts_core::RunId;
use argonauts_core::asking::AskReply;
use argonauts_core::events::run::RunOutcome;
use tokio::sync::oneshot;
use super::ConversationState;
use super::lifecycle::begin;
use crate::controller::Ctx;
use crate::controller::runs::RunNews;
use crate::controller::turn::TurnStep;
const GO_ON: &str = "Say so in one line and stop: its agents do the work, the user watches it, \
and its closing answer lands in this conversation.";
#[derive(Default)]
pub(super) struct Watched {
starting: Option<Starting>,
going: BTreeSet<RunId>,
untold: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Answer {
WhenUnderWay,
WhenTaken,
}
struct Starting {
run: Option<RunId>,
when: Answer,
what: String,
began: String,
reply: oneshot::Sender<AskReply>,
}
pub(super) fn hold(
conversation: &mut ConversationState,
reply: oneshot::Sender<AskReply>,
what: String,
began: String,
when: Answer,
) {
conversation.run_news.starting = Some(Starting {
run: None,
when,
what,
began,
reply,
});
}
pub(crate) fn run_started(
conversation: &mut ConversationState,
run: &RunId,
from_conversation: bool,
) {
let watched = &mut conversation.run_news;
if let Some(starting) = watched
.starting
.as_mut()
.filter(|starting| starting.run.is_none())
{
starting.run = Some(run.clone());
}
let taken = watched
.starting
.take_if(|starting| starting.when == Answer::WhenTaken);
if let Some(starting) = taken {
answer(starting, &RunNews::Began);
}
if from_conversation {
watched.going.insert(run.clone());
}
}
pub(crate) fn run_not_started(conversation: &mut ConversationState, err: &anyhow::Error) -> bool {
let Some(starting) = conversation
.run_news
.starting
.take_if(|starting| starting.run.is_none())
else {
return false;
};
let line = argonauts_core::events::failure::run_failure_line(Some(&format!("{err:#}")));
let text = format!(
"{} didn't start: {line}. Tell the user in one line that it didn't start and why, and \
stop.",
starting.what
);
if let Some(turn) = conversation.turn.as_mut() {
turn.run_began = false;
}
starting.reply.send(failure(text)).is_ok()
}
pub(crate) fn hear(
ctx: &mut Ctx,
conversation: &mut ConversationState,
news: Vec<(RunId, RunNews)>,
) {
for (run, news) in news {
let waiting = conversation
.run_news
.starting
.take_if(|starting| starting.run.as_ref() == Some(&run));
let told = match waiting {
Some(starting) => {
let (told, failed) = answer(starting, &news);
if failed && let Some(turn) = conversation.turn.as_mut() {
turn.run_began = false;
}
told
}
None => false,
};
if let RunNews::Ended { outcome, line } = news {
let ours = conversation.run_news.going.remove(&run);
if ours && !told && outcome == RunOutcome::Failed {
conversation.run_news.untold.push(failed_later(&run, &line));
}
}
}
tell(ctx, conversation);
}
fn answer(starting: Starting, news: &RunNews) -> (bool, bool) {
let Starting {
what, began, reply, ..
} = starting;
let reply_with = match news {
RunNews::Waiting { line } => AskReply::text(format!(
"{what} is waiting: {line}; nothing runs until then. Say so in one line and stop."
)),
RunNews::Began
| RunNews::Ended {
outcome: RunOutcome::Merged | RunOutcome::PullRequest | RunOutcome::BranchLeft,
..
} => AskReply::text(format!("{began} {GO_ON}")),
RunNews::Ended {
outcome: RunOutcome::Failed,
line,
} => failure(format!(
"{what} failed before any agent started: {line}. Tell the user in one line that it \
failed and why, and stop: nothing is running."
)),
RunNews::Ended { .. } => AskReply::text(format!(
"{what} was stopped before any agent started. Say so in one line and stop."
)),
};
let failed = reply_with.is_error;
(reply.send(reply_with).is_ok(), failed)
}
fn failure(text: String) -> AskReply {
AskReply {
text,
is_error: true,
}
}
fn failed_later(run: &RunId, line: &str) -> String {
format!(
"Argonauts Code, not the user, tells you: {run}, the run started for the user's request, \
failed: {line}. Tell the user in one line that it failed and why. Start nothing, since \
they decide what comes next."
)
}
fn tell(ctx: &mut Ctx, conversation: &mut ConversationState) {
if conversation.run_news.untold.is_empty()
|| conversation.turn.is_some()
|| !conversation.queued.is_empty()
{
return;
}
let text = std::mem::take(&mut conversation.run_news.untold).join("\n\n");
if let Err(err) = begin(ctx, conversation, text, |_| TurnStep::Told) {
ctx.error(&err);
}
}