use teloxide::types::{InlineKeyboardButton, InlineKeyboardMarkup};
use super::formatting::html_escape;
use crate::tools::command_risk::{PermissionMode, RiskLevel};
use crate::types::ApprovalResponse;
const MAX_CMD_DISPLAY: usize = 3600;
pub(super) fn approval_use_session_button(
permission_mode: PermissionMode,
risk_level: RiskLevel,
) -> bool {
match permission_mode {
PermissionMode::Cautious => true,
PermissionMode::Default => risk_level >= RiskLevel::Critical,
PermissionMode::Yolo => false,
}
}
pub(super) fn risk_header(risk_level: RiskLevel) -> (&'static str, &'static str, &'static str) {
match risk_level {
RiskLevel::Safe => (
"âšī¸",
"New command",
"Your agent wants to run something it hasn't done before.",
),
RiskLevel::Medium => (
"â ī¸",
"Approval needed",
"Your agent is waiting for your OK to continue.",
),
RiskLevel::High => (
"đļ",
"Review carefully",
"This action could have significant effects.",
),
RiskLevel::Critical => (
"đ¨",
"Dangerous action",
"Review carefully before allowing.",
),
}
}
pub(super) fn allow_button_hint(use_session_button: bool) -> &'static str {
if use_session_button {
"\"Allow Session\" means you won't be asked again until the daemon restarts."
} else {
"\"Allow Always\" means you won't be asked again for this type of action."
}
}
pub(super) fn response_status(response: &ApprovalResponse) -> (&'static str, &'static str) {
match response {
ApprovalResponse::AllowOnce => ("â
", "Allowed this once"),
ApprovalResponse::AllowSession => ("â
", "Allowed for this session"),
ApprovalResponse::AllowAlways => ("â
", "Always allowed"),
ApprovalResponse::Deny => ("â", "Denied"),
}
}
pub(super) fn response_status_detail(response: &ApprovalResponse) -> Option<&'static str> {
match response {
ApprovalResponse::AllowSession => Some("You'll be asked again after restart."),
ApprovalResponse::AllowAlways => Some("You won't be asked again for this type of action."),
_ => None,
}
}
fn strip_approval_footer(text: &str) -> String {
let mut body = text.trim_end().to_string();
for marker in [
"\n\n\"Allow Session\"",
"\n\n\"Allow Always\"",
"\n\n<i>\"Allow Session\"",
"\n\n<i>\"Allow Always\"",
"\n\n*\"Allow Session\"",
"\n\n*\"Allow Always\"",
"\n\n_\"Allow Session\"",
"\n\n_\"Allow Always\"",
] {
if let Some(idx) = body.find(marker) {
body.truncate(idx);
break;
}
}
body.trim_end().to_string()
}
pub(super) fn finalize_approval_message(original: &str, response: &ApprovalResponse) -> String {
let body = strip_approval_footer(original);
let (icon, status) = response_status(response);
let mut text = format!("{body}\n\n{icon} {status}");
if let Some(detail) = response_status_detail(response) {
text.push_str(&format!("\n{detail}"));
}
text
}
fn truncate_command(command: &str) -> String {
if command.len() > MAX_CMD_DISPLAY {
let end = crate::utils::floor_char_boundary(command, MAX_CMD_DISPLAY);
format!(
"{}...\n[truncated â {} chars total]",
&command[..end],
command.len()
)
} else {
command.to_string()
}
}
pub(super) fn build_approval_keyboard(
approval_id: &str,
use_session_button: bool,
) -> InlineKeyboardMarkup {
if use_session_button {
InlineKeyboardMarkup::new(vec![vec![
InlineKeyboardButton::callback("Allow Once", format!("approve:once:{}", approval_id)),
InlineKeyboardButton::callback(
"Allow Session",
format!("approve:session:{}", approval_id),
),
InlineKeyboardButton::callback("Deny", format!("approve:deny:{}", approval_id)),
]])
} else {
InlineKeyboardMarkup::new(vec![vec![
InlineKeyboardButton::callback("Allow Once", format!("approve:once:{}", approval_id)),
InlineKeyboardButton::callback(
"Allow Always",
format!("approve:always:{}", approval_id),
),
InlineKeyboardButton::callback("Deny", format!("approve:deny:{}", approval_id)),
]])
}
}
pub(super) fn build_approval_message_text(
command: &str,
risk_level: RiskLevel,
warnings: &[String],
use_session_button: bool,
) -> String {
let display_cmd = truncate_command(command);
let escaped_cmd = html_escape(&display_cmd);
let (risk_icon, risk_label, risk_subtitle) = risk_header(risk_level);
let mut text = format!(
"{} <b>{}</b>\n{}\n\n<b>Requested action</b>\n{}",
risk_icon, risk_label, risk_subtitle, escaped_cmd
);
if !warnings.is_empty() {
text.push('\n');
for warning in warnings {
text.push_str(&format!("\nâĸ {}", html_escape(warning)));
}
}
text.push_str(&format!(
"\n\n<i>{}</i>",
html_escape(allow_button_hint(use_session_button))
));
text
}
pub(super) fn build_approval_message_discord(
command: &str,
risk_level: RiskLevel,
warnings: &[String],
use_session_button: bool,
) -> String {
let display_cmd = truncate_command(command);
let (risk_icon, risk_label, risk_subtitle) = risk_header(risk_level);
let mut text = format!(
"{} **{}**\n{}\n\n**Requested action**\n```\n{display_cmd}\n```",
risk_icon, risk_label, risk_subtitle
);
if !warnings.is_empty() {
text.push('\n');
for warning in warnings {
text.push_str(&format!("\nâĸ {warning}"));
}
}
text.push_str(&format!("\n\n*{}*", allow_button_hint(use_session_button)));
text
}
pub(super) fn build_approval_message_slack(
command: &str,
risk_level: RiskLevel,
warnings: &[String],
use_session_button: bool,
) -> String {
let display_cmd = truncate_command(command);
let (risk_icon, risk_label, risk_subtitle) = risk_header(risk_level);
let mut text = format!(
"{} *{}*\n{}\n\n*Requested action*\n```{display_cmd}```",
risk_icon, risk_label, risk_subtitle
);
if !warnings.is_empty() {
text.push('\n');
for warning in warnings {
text.push_str(&format!("\nâĸ {warning}"));
}
}
text.push_str(&format!("\n\n_{}_", allow_button_hint(use_session_button)));
text
}
pub(super) fn build_goal_confirmation_keyboard(approval_id: &str) -> InlineKeyboardMarkup {
InlineKeyboardMarkup::new(vec![vec![
InlineKeyboardButton::callback("Confirm â
", format!("goal:confirm:{}", approval_id)),
InlineKeyboardButton::callback("Cancel â", format!("goal:cancel:{}", approval_id)),
]])
}
pub(super) fn build_goal_confirmation_text(goal_description: &str, details: &[String]) -> String {
let escaped_desc = html_escape(goal_description);
let mut text = format!(
"đ
<b>Confirm scheduled goal</b>\n\n<code>{}</code>",
escaped_desc
);
for detail in details {
text.push_str(&format!("\nâĸ {}", html_escape(detail)));
}
text
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn risk_header_medium_is_approval_needed() {
let (_, title, _) = risk_header(RiskLevel::Medium);
assert_eq!(title, "Approval needed");
}
#[test]
fn finalize_strips_footer_and_adds_status() {
let prompt = build_approval_message_text(
"Open website: https://example.com",
RiskLevel::Medium,
&[],
false,
);
let final_msg = finalize_approval_message(&prompt, &ApprovalResponse::AllowAlways);
assert!(!final_msg.contains("Allow Always\" means"));
assert!(final_msg.contains("Always allowed"));
assert!(final_msg.contains("won't be asked again"));
assert!(final_msg.contains("Open website"));
}
#[test]
fn finalize_denied_shows_denied_status() {
let prompt = build_approval_message_text("rm -rf /", RiskLevel::Critical, &[], true);
let final_msg = finalize_approval_message(&prompt, &ApprovalResponse::Deny);
assert!(final_msg.contains("â Denied"));
}
}