1use std::path::PathBuf;
4
5use async_trait::async_trait;
6use tokio::sync::mpsc;
7
8use crate::channels::{Channel, IncomingMessage, OutgoingMessage};
9
10#[derive(Debug, Clone, Default)]
12pub enum AgentMode {
13 #[default]
18 Auto,
19
20 BypassPermissions,
31
32 Coding {
40 plan_approval: bool,
41 project_path: Option<PathBuf>,
42 },
43
44 Swarm { parallelism: usize },
49}
50
51pub fn agent_mode_from_permission_profile(profile: &str) -> AgentMode {
53 let p = profile.trim().to_ascii_lowercase().replace(['-', ' '], "_");
54 match p.as_str() {
55 "full" => AgentMode::BypassPermissions,
56 "prompt" => AgentMode::Coding {
57 plan_approval: true,
58 project_path: None,
59 },
60 _ => AgentMode::Auto,
61 }
62}
63
64impl AgentMode {
65 pub fn is_coding(&self) -> bool {
66 matches!(self, Self::Coding { .. })
67 }
68
69 pub fn is_swarm(&self) -> bool {
70 matches!(self, Self::Swarm { .. })
71 }
72
73 pub fn bypass_permissions(&self) -> bool {
76 matches!(self, Self::BypassPermissions)
77 }
78
79 pub fn plan_approval(&self) -> bool {
82 if self.bypass_permissions() {
83 return false;
84 }
85 matches!(
86 self,
87 Self::Coding {
88 plan_approval: true,
89 ..
90 }
91 )
92 }
93
94 pub fn swarm_parallelism(&self) -> usize {
95 match self {
96 Self::Swarm { parallelism } => *parallelism,
97 _ => 1,
98 }
99 }
100
101 pub fn system_prompt_injection(&self) -> Option<&'static str> {
103 match self {
104 Self::BypassPermissions => Some(BYPASS_PERMISSIONS_PROMPT),
105 Self::Coding { .. } => Some(CODING_MODE_PROMPT),
106 Self::Swarm { .. } => Some(SWARM_MODE_PROMPT),
107 Self::Auto => None,
108 }
109 }
110
111 pub fn prefer_heavy_model(&self) -> bool {
113 matches!(
114 self,
115 Self::BypassPermissions | Self::Coding { .. } | Self::Swarm { .. }
116 )
117 }
118
119 pub fn always_plan(&self) -> bool {
121 matches!(
122 self,
123 Self::BypassPermissions | Self::Coding { .. } | Self::Swarm { .. }
124 )
125 }
126}
127
128pub struct NullChannel {
131 name: String,
132}
133
134impl NullChannel {
135 pub fn new(name: impl Into<String>) -> Self {
136 Self { name: name.into() }
137 }
138}
139
140#[async_trait]
141impl Channel for NullChannel {
142 fn name(&self) -> &str {
143 &self.name
144 }
145
146 async fn start(&mut self) -> anyhow::Result<mpsc::Receiver<IncomingMessage>> {
147 let (_tx, rx) = mpsc::channel(1);
148 Ok(rx) }
150
151 async fn send(&self, _message: OutgoingMessage) -> anyhow::Result<Option<String>> {
152 Ok(None)
153 }
154
155 async fn stop(&mut self) -> anyhow::Result<()> {
156 Ok(())
157 }
158}
159
160const BYPASS_PERMISSIONS_PROMPT: &str = "\
161## Autonomous Mode (Bypass Permissions)
162
163You are running with all permission checks disabled. You have full autonomy to:
164- Execute shell commands without confirmation
165- Read and write any files in the workspace
166- Make and push commits autonomously
167- Install dependencies and run builds
168
169Operate decisively. Do not ask for permission before taking actions. \
170Complete the task end-to-end and report what was done.";
171
172const CODING_MODE_PROMPT: &str = "\
173## Coding Mode Active
174
175You are in coding mode. Follow these rules:
176- Read files before editing them
177- Make surgical, minimal changes — don't reformat code you don't touch
178- After edits, verify correctness (cargo build, npm test, or equivalent)
179- Prefer targeted `edit` operations over full file rewrites
180- For multi-file changes: enumerate all affected files first, then execute in order
181- Diagnose root causes before retrying failures
182- Commit at natural checkpoints with clear, descriptive messages
183- Never break working code — test incrementally";
184
185const SWARM_MODE_PROMPT: &str = "\
186## Swarm Deployment Mode Active
187
188You are coordinating parallel coding agents. Follow these rules:
189- Decompose the goal into independent, non-overlapping subtasks
190- Assign each agent a clear, bounded scope (a file, module, or feature)
191- Avoid shared-state conflicts between agents
192- Validate and integrate each agent's output before merging
193- Reassign or handle failed tasks yourself
194- Report the outcome of each agent with status and a brief summary";
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_agent_mode_from_permission_profile() {
202 assert!(matches!(
203 agent_mode_from_permission_profile("full"),
204 AgentMode::BypassPermissions
205 ));
206 assert!(matches!(
207 agent_mode_from_permission_profile(" FULL "),
208 AgentMode::BypassPermissions
209 ));
210 assert!(matches!(
211 agent_mode_from_permission_profile("prompt"),
212 AgentMode::Coding {
213 plan_approval: true,
214 project_path: None
215 }
216 ));
217 assert!(matches!(
218 agent_mode_from_permission_profile(" ProMpT\n"),
219 AgentMode::Coding {
220 plan_approval: true,
221 project_path: None
222 }
223 ));
224 assert!(matches!(
225 agent_mode_from_permission_profile(""),
226 AgentMode::Auto
227 ));
228 assert!(matches!(
229 agent_mode_from_permission_profile("unknown-profile"),
230 AgentMode::Auto
231 ));
232 }
233}