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 "codeswarmstartupfullaccess",
66 "agyfullaccess",
67 ],
68 },
69];
70
71pub fn resolve(policy_id: &str, modes: &[Mode]) -> Option<Mode> {
72 POLICIES
73 .iter()
74 .find(|policy| policy.id == policy_id)
75 .and_then(|policy| policy.resolve(modes))
76}
77
78pub fn shared_modes(mode_sets: &[Vec<Mode>]) -> Vec<Mode> {
81 if mode_sets.is_empty() {
82 return Vec::new();
83 }
84 POLICIES
85 .iter()
86 .filter(|policy| {
87 mode_sets
88 .iter()
89 .all(|modes| policy.resolve(modes).is_some())
90 })
91 .map(ModePolicy::display_mode)
92 .collect()
93}
94
95pub fn shared_current_mode(states: &[(Vec<Mode>, Option<String>)]) -> Option<Mode> {
96 POLICIES.iter().find_map(|policy| {
97 states
98 .iter()
99 .all(|(modes, current)| {
100 current.as_ref().is_some_and(|current| {
101 policy
102 .resolve(modes)
103 .is_some_and(|native| native.id == *current)
104 })
105 })
106 .then(|| policy.display_mode())
107 })
108}
109
110fn normalize(value: &str) -> String {
111 value
112 .chars()
113 .filter(char::is_ascii_alphanumeric)
114 .flat_map(char::to_lowercase)
115 .collect()
116}
117
118#[cfg(test)]
119mod tests {
120 use crate::Mode;
121
122 use super::{DEFAULT_POLICY_ID, shared_current_mode, shared_modes};
123
124 #[test]
125 fn maps_different_native_names_to_one_policy() {
126 let sets = vec![
127 vec![Mode {
128 id: "yolo".into(),
129 label: "YOLO".into(),
130 }],
131 vec![Mode {
132 id: "full-access".into(),
133 label: "Auto pilot".into(),
134 }],
135 ];
136 let modes = shared_modes(&sets);
137 assert!(modes.iter().any(|mode| mode.id == DEFAULT_POLICY_ID));
138 }
139
140 #[test]
141 fn maps_codex_full_access_catalog_to_auto_pilot() {
142 let modes = vec![
143 Mode {
144 id: "read-only".into(),
145 label: "Ask for approval".into(),
146 },
147 Mode {
148 id: "agent".into(),
149 label: "Approve for me".into(),
150 },
151 Mode {
152 id: "agent-full-access".into(),
153 label: "Full access".into(),
154 },
155 ];
156 assert_eq!(
157 super::resolve(DEFAULT_POLICY_ID, &modes).map(|mode| mode.id),
158 Some("agent-full-access".into())
159 );
160 }
161
162 #[test]
163 fn maps_claude_auto_catalog_to_auto_pilot() {
164 let modes = vec![
165 Mode {
166 id: "default".into(),
167 label: "Manual".into(),
168 },
169 Mode {
170 id: "auto".into(),
171 label: "Auto".into(),
172 },
173 ];
174 assert_eq!(
175 super::resolve(DEFAULT_POLICY_ID, &modes).map(|mode| mode.id),
176 Some("auto".into())
177 );
178 }
179
180 #[test]
181 fn mixed_native_state_is_not_a_user_visible_mode() {
182 let states = vec![
183 (
184 vec![Mode {
185 id: "plan".into(),
186 label: "Plan".into(),
187 }],
188 Some("plan".into()),
189 ),
190 (
191 vec![Mode {
192 id: "default".into(),
193 label: "Manual".into(),
194 }],
195 Some("default".into()),
196 ),
197 ];
198 assert_eq!(shared_current_mode(&states), None);
199 }
200}