use argonauts_core::app::AppMode;
use argonauts_core::events::RunEventKind;
use argonauts_core::events::approval::ApprovalKind;
use argonauts_core::events::run::{RunOutcome, WaitReason};
use argonauts_core::events::{
ApprovalEvent, RunEvent, RunLifecycle, SessionEvent, SpendEvent, WorkflowEvent,
};
use argonauts_core::names::{ApprovalId, MessageId};
use argonauts_core::settings::ui::ConversationTrigger;
use crate::controller::attach::{Attached, Stage};
#[derive(Debug, Clone, PartialEq)]
pub(super) struct Waiting {
pub approval: ApprovalId,
pub kind: ApprovalKind,
}
#[derive(Debug, Default)]
pub(super) struct Seen {
pub mode: Option<AppMode>,
pub status: bool,
pub waiting: Option<Waiting>,
pub finished: Option<RunOutcome>,
pub detail: Option<String>,
pub delivered: Option<MessageId>,
pub began: bool,
pub held: Option<String>,
}
fn held_words(reason: &WaitReason) -> Option<String> {
match reason {
WaitReason::Approval | WaitReason::Paused => None,
WaitReason::AllAgentsOut { .. } => {
Some("every agent's CLI is out of plan usage or down until one comes back".to_owned())
}
WaitReason::WaitingForAgent { agent_id, .. } => Some(format!(
"its tasks wait for {} to come back",
argonauts_core::display_names::DisplayNames::default().cli(&agent_id.0)
)),
WaitReason::NoFallback { detail, .. } => Some(detail.clone()),
}
}
pub(super) fn track(attached: &mut Attached, event: &RunEvent) -> Seen {
let mut seen = Seen::default();
let run = attached.run.clone();
let mode = match &event.kind {
RunEventKind::Run(lifecycle) => match lifecycle {
RunLifecycle::Named(_) => None,
RunLifecycle::Started(_) | RunLifecycle::Resumed(_) | RunLifecycle::PauseCancelled => {
attached.stage = Stage::Going;
attached.crashed = false;
attached.left_behind = false;
Some(AppMode::Working { run })
}
RunLifecycle::PauseRequested { .. } => {
attached.stage = Stage::Going;
Some(AppMode::Pausing { run })
}
RunLifecycle::Waiting { reason, .. } => {
attached.stage = Stage::Waiting;
match held_words(reason) {
Some(words) => seen.held = Some(words),
None => seen.began = true,
}
Some(AppMode::Waiting {
run,
reason: reason.clone(),
})
}
RunLifecycle::Finished { outcome, detail } => {
attached.stage = Stage::Finished(*outcome);
seen.finished = Some(*outcome);
seen.detail = detail.clone();
Some(AppMode::Idle)
}
RunLifecycle::ApprovalModeChanged { mode, .. } => {
attached.approval_mode = Some(*mode);
seen.status = true;
None
}
RunLifecycle::Paused { .. }
| RunLifecycle::StopRequested { .. }
| RunLifecycle::Snapshot(_)
| RunLifecycle::ClosingAnswer(_) => None,
},
RunEventKind::Spend(SpendEvent::Run(limit)) => {
seen.status = attached.spent != Some(limit.spent) || attached.limit != limit.limit;
attached.spent = Some(limit.spent);
attached.limit = limit.limit;
None
}
RunEventKind::Approval(ApprovalEvent::Opened(opened)) if opened.needs_you => {
seen.waiting = Some(Waiting {
approval: opened.id.clone(),
kind: opened.kind,
});
seen.began = true;
None
}
RunEventKind::Session(SessionEvent::Delivered { message, .. }) => {
seen.delivered = Some(message.clone());
None
}
RunEventKind::Session(SessionEvent::Started(_))
| RunEventKind::Workflow(WorkflowEvent::StepFinished(_)) => {
seen.began = true;
None
}
_ => None,
};
if let Some(mode) = mode
&& mode != attached.mode
{
attached.mode = mode.clone();
seen.mode = Some(mode);
}
seen
}
pub(super) fn trigger(kind: ApprovalKind) -> ConversationTrigger {
match kind {
ApprovalKind::AgentDown => ConversationTrigger::AgentDown,
_ => ConversationTrigger::NeedsYou,
}
}
pub(super) fn approval_label(kind: ApprovalKind) -> &'static str {
match kind {
ApprovalKind::Understand => "Understand approval",
ApprovalKind::Plan => "plan approval",
ApprovalKind::Merge => "merge approval",
ApprovalKind::Fallback => "fallback question",
ApprovalKind::AgentDown => "agent down",
ApprovalKind::Stuck => "task out of tries",
ApprovalKind::Limit => "spend limit",
ApprovalKind::ChangeRequest => "change request",
ApprovalKind::GoalChange => "goal change",
ApprovalKind::WorkflowPromotion => "workflow promotion",
ApprovalKind::TakeOverReturn => "return from a take-over",
ApprovalKind::Question => "question from an agent",
ApprovalKind::OriginalReturned => "original agent back",
}
}
pub(super) fn outcome_label(outcome: RunOutcome) -> &'static str {
match outcome {
RunOutcome::Merged => "merged",
RunOutcome::PullRequest => "pull request",
RunOutcome::BranchLeft => "branches left",
RunOutcome::Abandoned => "abandoned",
RunOutcome::Stopped => "stopped",
RunOutcome::Failed => "failed",
}
}
pub(super) fn outcome_sentence(run: impl std::fmt::Display, outcome: RunOutcome) -> String {
match outcome {
RunOutcome::Merged => format!("{run} is merged."),
RunOutcome::PullRequest => format!("{run} finished with a pull request."),
RunOutcome::BranchLeft => format!("{run} finished with its branches left for later."),
RunOutcome::Abandoned => format!("{run} was abandoned, and its work is removed."),
RunOutcome::Stopped => format!("{run} stopped, and its work is committed."),
RunOutcome::Failed => format!("{run} failed."),
}
}