illumitech-forge-cli 1.4.0

Evidence-backed agent execution CLI
Documentation
use super::{Line, Session, display, markdown};
use ratatui::style::{Color, Style};
use serde_json::Value;
use std::collections::VecDeque;
fn entries(session: Option<&Session>) -> Vec<(&'static str, String)> {
    let mut lines = Vec::new();
    let mut agents = std::collections::BTreeMap::<String, String>::new();
    let Some(session) = session else {
        return Vec::new();
    };
    // Collapse deltas into their item, retaining chronological placement and final text.
    for e in &session.events {
        let p = &e["params"];
        let method = e["method"].as_str().unwrap_or("");
        if method == "item/agentMessage/delta" {
            agents
                .entry(p["itemId"].as_str().unwrap_or("").into())
                .or_default()
                .push_str(p["delta"].as_str().unwrap_or(""));
        } else if method == "item/completed" && p["item"]["type"] == "agentMessage" {
            agents.insert(
                p["item"]["id"].as_str().unwrap_or("").into(),
                p["item"]["text"].as_str().unwrap_or("").into(),
            );
        }
    }
    let mut seen = std::collections::BTreeSet::new();
    let activities = super::activity::items(&session.events);
    let mut seen_activities = std::collections::BTreeSet::new();
    let mut echo = None;
    for e in &session.events {
        let p = &e["params"];
        let method = e["method"].as_str().unwrap_or("");
        let item = &p["item"];
        let kind = item["type"].as_str().unwrap_or("");
        let (label, text) = if method == "bridge/user" {
            let text = p["text"].as_str().unwrap_or("").to_owned();
            echo = Some(text.clone());
            ("Você", text)
        } else if method == "item/completed" && kind == "userMessage" {
            let text = item["text"].as_str().map(str::to_owned).unwrap_or_else(|| {
                item["content"]
                    .as_array()
                    .map(|a| {
                        a.iter()
                            .filter_map(|v| v["text"].as_str())
                            .collect::<Vec<_>>()
                            .join("\n")
                    })
                    .unwrap_or_default()
            });
            if echo.as_ref() == Some(&text) {
                echo = None;
                continue;
            }
            ("Você", text)
        } else if method == "item/agentMessage/delta"
            || method == "item/completed" && kind == "agentMessage"
        {
            let id = p["itemId"].as_str().or(item["id"].as_str()).unwrap_or("");
            if !seen.insert(id.to_owned()) {
                continue;
            }
            ("Forge", agents.get(id).cloned().unwrap_or_default())
        } else if method == "turn/completed" {
            echo = None;
            (
                "Estado",
                match p["turn"]["status"].as_str().unwrap_or("") {
                    "completed" => "Resposta concluída",
                    "interrupted" => "Resposta interrompida",
                    "failed" => "Não foi possível concluir a resposta",
                    _ => "Resposta encerrada",
                }
                .into(),
            )
        } else if method == "error" || method == "bridge/truncated" {
            (
                "Atenção",
                p["error"]["message"]
                    .as_str()
                    .or(p["reason"].as_str())
                    .unwrap_or("Consulte os detalhes do cliente.")
                    .into(),
            )
        } else if method == "bridge/decision" {
            ("Decisão enviada", super::decision_label(&p["decision"]))
        } else if matches!(
            method,
            "item/started"
                | "item/completed"
                | "item/commandExecution/outputDelta"
                | "item/fileChange/outputDelta"
                | "item/mcpToolCall/progress"
        ) {
            if matches!(kind, "userMessage" | "agentMessage" | "reasoning") {
                continue;
            }
            let id = item["id"].as_str().or(p["itemId"].as_str()).unwrap_or("");
            let Some(activity) = activities.get(id) else {
                continue;
            };
            if !seen_activities.insert(id.to_owned()) {
                continue;
            }
            ("Atividade", super::activity::entry(activity))
        } else {
            continue;
        };
        if label == "Atividade"
            && text.starts_with("reasoning|")
            && text
                .split_once('\n')
                .is_some_and(|(_, output)| output.trim().is_empty())
        {
            continue;
        }
        lines.push((label, text));
        if method == "turn/completed" && !p["turn"]["error"].is_null() {
            lines.push((
                "Atenção",
                p["turn"]["error"]["message"]
                    .as_str()
                    .map(str::to_owned)
                    .unwrap_or_else(|| p["turn"]["error"].to_string()),
            ));
        }
    }
    lines
}

fn render(session: Option<&Session>, details: bool) -> Vec<Line<'static>> {
    let mut lines = Vec::new();
    for (label, text) in entries(session) {
        if label == "Estado" {
            lines.push(Line::styled(text, Style::default().fg(Color::DarkGray)));
            lines.push(Line::default());
            continue;
        }
        if label == "Atividade" {
            let (tag, output) = text.split_once('\n').unwrap_or((&text, ""));
            let (title, status) = tag.split_once('|').unwrap_or((tag, ""));
            let state = match status {
                "failed" => " · falhou",
                "completed" => " · concluída",
                "interrupted" => " · interrompida",
                "declined" => " · recusada",
                "inProgress" => " · em andamento",
                _ => "",
            };
            let title = display(title);
            lines.push(Line::styled(
                format!(
                    "  › {title}{state}{}",
                    if details {
                        ""
                    } else {
                        " · Ctrl+O para detalhes"
                    }
                ),
                Style::default().fg(markdown::TEAL),
            ));
            if details {
                lines.extend(
                    display(output)
                        .lines()
                        .map(|s| Line::from(format!("    {s}"))),
                );
            }
            continue;
        }
        lines.push(Line::styled(
            label.to_owned(),
            Style::default()
                .fg(if label == "Você" {
                    markdown::TEAL
                } else {
                    markdown::ORANGE
                })
                .bold(),
        ));
        if label == "Forge" {
            lines.extend(markdown::render(&text));
        } else {
            lines.extend(display(&text).lines().map(|s| Line::from(s.to_owned())));
        }
        lines.push(Line::default());
    }
    lines
}
#[derive(Default)]
pub(super) struct Cache {
    id: String,
    events: VecDeque<Value>,
    details: bool,
    pub lines: Vec<Line<'static>>,
    pub history: Vec<String>,
}
impl Cache {
    pub fn update(&mut self, session: Option<&Session>, details: bool) {
        let id = session.map(|s| s.id.as_str()).unwrap_or("");
        let changed = self.id != id
            || session.is_some_and(|s| s.events != self.events)
            || (session.is_none() && !self.events.is_empty());
        if changed || self.details != details {
            self.id = id.into();
            self.events = session.map(|s| s.events.clone()).unwrap_or_default();
            self.details = details;
            self.lines = render(session, details);
            let mut history = entries(session)
                .into_iter()
                .filter(|(label, _)| *label == "Você")
                .rev()
                .map(|(_, text)| text)
                .take(50)
                .collect::<Vec<_>>();
            let mut bytes = 0;
            history.retain(|s| {
                bytes += s.len();
                bytes <= 256 * 1024
            });
            history.reverse();
            self.history = history;
        }
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;
    #[test]
    fn streaming_tools_and_files_have_one_summary_and_opt_in_payloads() {
        let session = Session { events: [
            json!({"method":"item/started","params":{"item":{"id":"cmd","type":"commandExecution","command":"cargo check","cwd":"/project"}}}),
            json!({"method":"item/commandExecution/outputDelta","params":{"itemId":"cmd","delta":"PARTIAL_OUTPUT"}}),
            json!({"method":"item/completed","params":{"item":{"id":"cmd","type":"commandExecution","status":"completed","exitCode":0,"aggregatedOutput":"FINAL_OUTPUT"}}}),
            json!({"method":"item/completed","params":{"item":{"id":"mcp","type":"mcpToolCall","server":"logs","tool":"query","arguments":{"status":500},"result":{"message":"\u{1b}[2JPRIVATE_RESULT"},"status":"completed"}}}),
            json!({"method":"item/completed","params":{"item":{"id":"files","type":"fileChange","status":"completed","changes":[{"path":"src/test.rs","diff":"@@ -1 +1 @@\n-old\n+new\n"}]}}}),
        ].into(), ..Default::default() };
        let collapsed = render(Some(&session), false)
            .iter()
            .map(ToString::to_string)
            .collect::<String>();
        assert_eq!(collapsed.matches("Comando · cargo check").count(), 1);
        assert!(collapsed.contains("Chamada · logs.query"));
        assert!(collapsed.contains("+1 −1"));
        assert!(!collapsed.contains("PRIVATE_RESULT"));
        let expanded = render(Some(&session), true)
            .iter()
            .map(ToString::to_string)
            .collect::<String>();
        assert!(expanded.contains("Argumentos"));
        assert!(expanded.contains("Resultado"));
        assert!(expanded.contains("FINAL_OUTPUT"));
        assert!(!expanded.contains("PARTIAL_OUTPUT"));
        assert!(expanded.contains("src/test.rs"));
        assert!(!expanded.contains('\u{1b}'));
    }
    #[test]
    fn streaming_replaces_partial_and_activity_details_are_opt_in() {
        let session=Session{events:[json!({"method":"bridge/user","params":{"text":"pedido"}}),json!({"method":"item/agentMessage/delta","params":{"itemId":"a","delta":"parcial"}}),json!({"method":"item/completed","params":{"item":{"id":"a","type":"agentMessage","text":"**Resposta** completa"}}}),json!({"method":"item/completed","params":{"item":{"id":"tool","type":"commandExecution","aggregatedOutput":"SAIDA_PRIVADA_DO_TESTE"}}})].into(),..Default::default()};
        let mut cache = Cache::default();
        cache.update(Some(&session), false);
        let text = cache
            .lines
            .iter()
            .map(ToString::to_string)
            .collect::<String>();
        assert!(text.contains("Resposta completa"));
        assert!(!text.contains("parcial"));
        assert!(!text.contains("SAIDA_PRIVADA_DO_TESTE"));
        assert_eq!(cache.history, vec!["pedido"]);
        cache.update(Some(&session), true);
        assert!(
            cache
                .lines
                .iter()
                .map(ToString::to_string)
                .collect::<String>()
                .contains("SAIDA_PRIVADA_DO_TESTE")
        );
        cache.update(None, false);
        assert!(cache.history.is_empty());
    }
}