1use crate::Mode;
4
5pub const DEFAULT_POLICY_ID: &str = "codeswarm:mode:full-access";
6
7#[derive(Clone, Debug, Eq, PartialEq)]
8pub struct ModePolicy {
9 pub id: &'static str,
10 pub name: &'static str,
11 pub description: &'static str,
12 aliases: &'static [&'static str],
13}
14
15impl ModePolicy {
16 pub fn resolve(&self, modes: &[Mode]) -> Option<Mode> {
17 modes.iter().find_map(|mode| {
18 let id = normalize(&mode.id);
19 let label = normalize(&mode.label);
20 self.aliases
21 .iter()
22 .any(|alias| *alias == id || *alias == label)
23 .then(|| mode.clone())
24 })
25 }
26
27 pub fn display_mode(&self) -> Mode {
28 Mode {
29 id: self.id.into(),
30 label: self.name.into(),
31 }
32 }
33}
34
35pub const POLICIES: &[ModePolicy] = &[
36 ModePolicy {
37 id: "codeswarm:mode:plan",
38 name: "Plan",
39 description: "Read-only planning with no tool execution",
40 aliases: &["plan", "planmode", "readonly"],
41 },
42 ModePolicy {
43 id: "codeswarm:mode:manual",
44 name: "Manual",
45 description: "Ask before operations that require permission",
46 aliases: &["default", "manual", "ask", "agymanual"],
47 },
48 ModePolicy {
49 id: "codeswarm:mode:accept-edits",
50 name: "Accept Edits",
51 description: "Automatically approve file edits, but keep safeguards",
52 aliases: &["acceptedits", "autoedit", "autoapproveedits"],
53 },
54 ModePolicy {
55 id: DEFAULT_POLICY_ID,
56 name: "Auto pilot",
57 description: "Automatically approve tools and bypass prompts",
58 aliases: &[
59 "auto",
60 "autopilot",
61 "fullaccess",
62 "yolo",
63 "bypasspermissions",
64 "skippermissions",
65 "build",
67 "codeswarmstartupfullaccess",
68 "agyfullaccess",
69 ],
70 },
71];
72
73pub fn resolve(policy_id: &str, modes: &[Mode]) -> Option<Mode> {
74 POLICIES
75 .iter()
76 .find(|policy| policy.id == policy_id)
77 .and_then(|policy| policy.resolve(modes))
78}
79
80pub fn shared_modes(mode_sets: &[Vec<Mode>]) -> Vec<Mode> {
83 if mode_sets.is_empty() {
84 return Vec::new();
85 }
86 POLICIES
87 .iter()
88 .filter(|policy| {
89 mode_sets
90 .iter()
91 .all(|modes| policy.resolve(modes).is_some())
92 })
93 .map(ModePolicy::display_mode)
94 .collect()
95}
96
97pub fn shared_current_mode(states: &[(Vec<Mode>, Option<String>)]) -> Option<Mode> {
98 POLICIES.iter().find_map(|policy| {
99 states
100 .iter()
101 .all(|(modes, current)| {
102 current.as_ref().is_some_and(|current| {
103 policy
104 .resolve(modes)
105 .is_some_and(|native| native.id == *current)
106 })
107 })
108 .then(|| policy.display_mode())
109 })
110}
111
112fn normalize(value: &str) -> String {
113 value
114 .chars()
115 .filter(char::is_ascii_alphanumeric)
116 .flat_map(char::to_lowercase)
117 .collect()
118}
119
120#[cfg(test)]
121mod tests {
122 use crate::Mode;
123
124 use super::{DEFAULT_POLICY_ID, shared_current_mode, shared_modes};
125
126 #[test]
127 fn maps_different_native_names_to_one_policy() {
128 let sets = vec![
129 vec![Mode {
130 id: "yolo".into(),
131 label: "YOLO".into(),
132 }],
133 vec![Mode {
134 id: "full-access".into(),
135 label: "Auto pilot".into(),
136 }],
137 ];
138 let modes = shared_modes(&sets);
139 assert!(modes.iter().any(|mode| mode.id == DEFAULT_POLICY_ID));
140 }
141
142 #[test]
143 fn maps_codex_full_access_catalog_to_auto_pilot() {
144 let modes = vec![
145 Mode {
146 id: "read-only".into(),
147 label: "Ask for approval".into(),
148 },
149 Mode {
150 id: "agent".into(),
151 label: "Approve for me".into(),
152 },
153 Mode {
154 id: "agent-full-access".into(),
155 label: "Full access".into(),
156 },
157 ];
158 assert_eq!(
159 super::resolve(DEFAULT_POLICY_ID, &modes).map(|mode| mode.id),
160 Some("agent-full-access".into())
161 );
162 }
163
164 #[test]
165 fn maps_opencode_build_catalog_to_auto_pilot() {
166 let modes = vec![
167 Mode {
168 id: "build".into(),
169 label: "build".into(),
170 },
171 Mode {
172 id: "plan".into(),
173 label: "plan".into(),
174 },
175 ];
176 assert_eq!(
177 super::resolve(DEFAULT_POLICY_ID, &modes).map(|mode| mode.id),
178 Some("build".into())
179 );
180 }
181
182 #[test]
183 fn maps_claude_auto_catalog_to_auto_pilot() {
184 let modes = vec![
185 Mode {
186 id: "default".into(),
187 label: "Manual".into(),
188 },
189 Mode {
190 id: "auto".into(),
191 label: "Auto".into(),
192 },
193 ];
194 assert_eq!(
195 super::resolve(DEFAULT_POLICY_ID, &modes).map(|mode| mode.id),
196 Some("auto".into())
197 );
198 }
199
200 #[test]
201 fn mixed_native_state_is_not_a_user_visible_mode() {
202 let states = vec![
203 (
204 vec![Mode {
205 id: "plan".into(),
206 label: "Plan".into(),
207 }],
208 Some("plan".into()),
209 ),
210 (
211 vec![Mode {
212 id: "default".into(),
213 label: "Manual".into(),
214 }],
215 Some("default".into()),
216 ),
217 ];
218 assert_eq!(shared_current_mode(&states), None);
219 }
220}