use bevy_ecs::prelude::*;
use rig_ecs::agent::{SpawnedBy, TurnOf, TurnOutcome};
use rig_ecs::stage::{ForAgent, Outcome, Settled};
use rig_harness::ask::Ask;
use rig_harness::pick::Picker;
use super::terminal::TerminalWrite;
const PUSH_TITLE: &[u8] = b"\x1b[22;0t";
const POP_TITLE: &[u8] = b"\x1b[23;0t";
#[derive(Resource)]
pub(crate) struct Told {
running: Option<bool>,
title: Option<&'static str>,
failed: bool,
options: String,
}
impl Default for Told {
fn default() -> Self {
let nonce = std::env::var("FUX_MARK_NONCE").ok();
Self {
running: None,
title: None,
failed: false,
options: nonce
.map(|nonce| format!(";nonce={nonce}"))
.unwrap_or_default(),
}
}
}
impl Told {
fn mark(&self, mark: &str) -> String {
format!("\x1b]133;{mark}{}\x1b\\", self.options)
}
fn start(&mut self) -> String {
self.running = Some(true);
self.mark("C") + &self.mark("S;activity=busy")
}
fn idle(&mut self) -> String {
let end = match self.running {
Some(true) => self.mark(if self.failed { "D;1" } else { "D;0" }),
Some(false) | None => String::new(),
};
self.running = Some(false);
let prompt = self.mark("A") + &self.mark("B");
end + "\x1b7\r" + &prompt + "\x1b8" + &self.mark("S;activity=reading")
}
}
pub(crate) fn turn_started(
turn: On<Add<TurnOf>>,
turns: Query<&TurnOf>,
subagents: Query<(), With<SpawnedBy>>,
mut told: ResMut<Told>,
mut out: MessageWriter<TerminalWrite>,
) {
let Ok(&TurnOf(agent)) = turns.get(turn.entity) else {
return;
};
if !subagents.contains(agent) && told.running != Some(true) {
out.write(TerminalWrite(told.start().into_bytes()));
}
}
pub(crate) fn turn_ended(
settled: On<Add<Settled>>,
turns: Query<(&ForAgent, &TurnOutcome)>,
subagents: Query<(), With<SpawnedBy>>,
mut told: ResMut<Told>,
mut out: MessageWriter<TerminalWrite>,
) {
let Ok((&ForAgent(agent), outcome)) = turns.get(settled.entity) else {
return;
};
if !subagents.contains(agent) && told.running == Some(true) {
told.failed = !matches!(outcome, TurnOutcome::Answered(_));
out.write(TerminalWrite(told.idle().into_bytes()));
}
}
pub(crate) fn tell_state(
turns: Query<&TurnOf, Without<Settled>>,
subagents: Query<(), With<SpawnedBy>>,
pickers: Query<(), With<Picker>>,
asks: Query<(), (With<Ask>, Without<Outcome>)>,
mut told: ResMut<Told>,
mut out: MessageWriter<TerminalWrite>,
) {
let running = turns.iter().any(|turn| !subagents.contains(turn.0));
if told.running.is_none() || told.running == Some(true) && !running {
if told.running.is_some() {
told.failed = true;
}
out.write(TerminalWrite(told.idle().into_bytes()));
}
let state = if !pickers.is_empty() || !asks.is_empty() {
"needs you"
} else if running {
"working"
} else if told.failed {
"error"
} else {
"idle"
};
if told.title == Some(state) {
return;
}
let mut bytes = match told.title {
None => PUSH_TITLE.to_vec(),
Some(_) => Vec::new(),
};
bytes.extend_from_slice(format!("\x1b]2;rig · {state}\x1b\\").as_bytes());
out.write(TerminalWrite(bytes));
told.title = Some(state);
}
pub(crate) fn restore_title(mut told: ResMut<Told>, mut out: MessageWriter<TerminalWrite>) {
if told.title.take().is_some() {
out.write(TerminalWrite(POP_TITLE.to_vec()));
}
}
#[cfg(test)]
mod tests;