use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use async_trait::async_trait;
use tokio::sync::mpsc;
use crate::channels::{Channel, IncomingMessage, OutgoingMessage};
#[derive(Debug, Clone, Default)]
pub enum AgentMode {
#[default]
Auto,
BypassPermissions,
Coding {
plan_approval: bool,
project_path: Option<PathBuf>,
},
Swarm { parallelism: usize },
}
pub fn agent_mode_from_permission_profile(profile: &str) -> AgentMode {
let p = profile.trim().to_ascii_lowercase().replace(['-', ' '], "_");
match p.as_str() {
"full" => AgentMode::BypassPermissions,
"prompt" => AgentMode::Coding {
plan_approval: true,
project_path: None,
},
_ => AgentMode::Auto,
}
}
impl AgentMode {
pub fn is_coding(&self) -> bool {
matches!(self, Self::Coding { .. })
}
pub fn is_swarm(&self) -> bool {
matches!(self, Self::Swarm { .. })
}
pub fn bypass_permissions(&self) -> bool {
matches!(self, Self::BypassPermissions)
}
pub fn plan_approval(&self) -> bool {
if self.bypass_permissions() {
return false;
}
matches!(
self,
Self::Coding {
plan_approval: true,
..
}
)
}
pub fn swarm_parallelism(&self) -> usize {
match self {
Self::Swarm { parallelism } => *parallelism,
_ => 1,
}
}
pub fn system_prompt_injection(&self) -> Option<&'static str> {
match self {
Self::BypassPermissions => Some(BYPASS_PERMISSIONS_PROMPT),
Self::Coding { .. } => Some(CODING_MODE_PROMPT),
Self::Swarm { .. } => Some(SWARM_MODE_PROMPT),
Self::Auto => None,
}
}
pub fn prefer_heavy_model(&self) -> bool {
matches!(
self,
Self::BypassPermissions | Self::Coding { .. } | Self::Swarm { .. }
)
}
pub fn always_plan(&self) -> bool {
matches!(
self,
Self::BypassPermissions | Self::Coding { .. } | Self::Swarm { .. }
)
}
}
#[derive(Debug, Clone)]
pub struct PendingPlan {
pub plan: String,
pub original_message: String,
pub preferred_model: String,
pub created_at: Instant,
}
impl PendingPlan {
const TTL: Duration = Duration::from_secs(30 * 60);
pub fn is_expired(&self) -> bool {
self.created_at.elapsed() > Self::TTL
}
}
pub type PendingPlans = Arc<Mutex<HashMap<String, PendingPlan>>>;
pub fn is_approval(text: &str) -> bool {
let lower = text.trim().to_lowercase();
matches!(
lower.as_str(),
"yes"
| "y"
| "go"
| "ok"
| "okay"
| "proceed"
| "approve"
| "do it"
| "looks good"
| "lgtm"
| "go ahead"
| "continue"
| "run it"
| "execute"
) || lower.starts_with("yes,")
|| lower.starts_with("go ahead")
|| lower.starts_with("looks good")
|| lower.starts_with("lgtm")
}
pub fn is_rejection(text: &str) -> bool {
let lower = text.trim().to_lowercase();
matches!(
lower.as_str(),
"no" | "n" | "nope" | "cancel" | "abort" | "stop" | "nevermind" | "never mind"
) || lower.starts_with("no,")
|| lower.starts_with("cancel")
|| lower.starts_with("stop ")
}
pub struct NullChannel {
name: String,
}
impl NullChannel {
pub fn new(name: impl Into<String>) -> Self {
Self { name: name.into() }
}
}
#[async_trait]
impl Channel for NullChannel {
fn name(&self) -> &str {
&self.name
}
async fn start(&mut self) -> anyhow::Result<mpsc::Receiver<IncomingMessage>> {
let (_tx, rx) = mpsc::channel(1);
Ok(rx) }
async fn send(&self, _message: OutgoingMessage) -> anyhow::Result<Option<String>> {
Ok(None)
}
async fn stop(&mut self) -> anyhow::Result<()> {
Ok(())
}
}
const BYPASS_PERMISSIONS_PROMPT: &str = "\
## Autonomous Mode (Bypass Permissions)
You are running with all permission checks disabled. You have full autonomy to:
- Execute shell commands without confirmation
- Read and write any files in the workspace
- Make and push commits autonomously
- Install dependencies and run builds
Operate decisively. Do not ask for permission before taking actions. \
Complete the task end-to-end and report what was done.";
const CODING_MODE_PROMPT: &str = "\
## Coding Mode Active
You are in coding mode. Follow these rules:
- Read files before editing them
- Make surgical, minimal changes — don't reformat code you don't touch
- After edits, verify correctness (cargo build, npm test, or equivalent)
- Prefer targeted `edit` operations over full file rewrites
- For multi-file changes: enumerate all affected files first, then execute in order
- Diagnose root causes before retrying failures
- Commit at natural checkpoints with clear, descriptive messages
- Never break working code — test incrementally";
const SWARM_MODE_PROMPT: &str = "\
## Swarm Deployment Mode Active
You are coordinating parallel coding agents. Follow these rules:
- Decompose the goal into independent, non-overlapping subtasks
- Assign each agent a clear, bounded scope (a file, module, or feature)
- Avoid shared-state conflicts between agents
- Validate and integrate each agent's output before merging
- Reassign or handle failed tasks yourself
- Report the outcome of each agent with status and a brief summary";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agent_mode_from_permission_profile() {
assert!(matches!(
agent_mode_from_permission_profile("full"),
AgentMode::BypassPermissions
));
assert!(matches!(
agent_mode_from_permission_profile(" FULL "),
AgentMode::BypassPermissions
));
assert!(matches!(
agent_mode_from_permission_profile("prompt"),
AgentMode::Coding {
plan_approval: true,
project_path: None
}
));
assert!(matches!(
agent_mode_from_permission_profile(" ProMpT\n"),
AgentMode::Coding {
plan_approval: true,
project_path: None
}
));
assert!(matches!(
agent_mode_from_permission_profile(""),
AgentMode::Auto
));
assert!(matches!(
agent_mode_from_permission_profile("unknown-profile"),
AgentMode::Auto
));
}
#[test]
fn test_is_approval() {
assert!(is_approval("yes"));
assert!(is_approval("y"));
assert!(is_approval("go"));
assert!(is_approval("ok"));
assert!(is_approval("okay"));
assert!(is_approval("proceed"));
assert!(is_approval("approve"));
assert!(is_approval("do it"));
assert!(is_approval("looks good"));
assert!(is_approval("lgtm"));
assert!(is_approval("go ahead"));
assert!(is_approval("continue"));
assert!(is_approval("run it"));
assert!(is_approval("execute"));
assert!(is_approval("YES"));
assert!(is_approval("OkAy"));
assert!(is_approval("PROCEED"));
assert!(is_approval("LGTM"));
assert!(is_approval(" yes "));
assert!(is_approval("\tgo ahead\n"));
assert!(is_approval("yes, do it"));
assert!(is_approval("go ahead and run"));
assert!(is_approval("looks good to me"));
assert!(is_approval("lgtm, go for it"));
assert!(!is_approval("no"));
assert!(!is_approval("yep"));
assert!(!is_approval("sure"));
assert!(!is_approval(""));
assert!(!is_approval(" "));
assert!(!is_approval("go ahea")); }
#[test]
fn test_is_rejection() {
assert!(is_rejection("no"));
assert!(is_rejection("n"));
assert!(is_rejection("nope"));
assert!(is_rejection("cancel"));
assert!(is_rejection("abort"));
assert!(is_rejection("stop"));
assert!(is_rejection("nevermind"));
assert!(is_rejection("never mind"));
assert!(is_rejection("NO"));
assert!(is_rejection("Cancel"));
assert!(is_rejection("ABORT"));
assert!(is_rejection("STOP"));
assert!(is_rejection(" no "));
assert!(is_rejection("\tabort\n"));
assert!(is_rejection("no, don't do that"));
assert!(is_rejection("cancel the operation"));
assert!(is_rejection("stop executing"));
assert!(!is_rejection("yes"));
assert!(!is_rejection("not really"));
assert!(!is_rejection(""));
assert!(!is_rejection(" "));
assert!(!is_rejection("stopit")); }
}