Skip to content

Commit 27fefe5

Browse files
committed
fix: sync steer toggle to config.toml
1 parent 42b86f5 commit 27fefe5

File tree

5 files changed

+159
-5
lines changed

5 files changed

+159
-5
lines changed

src-tauri/src/bin/codex_monitor_daemon.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#[path = "../backend/mod.rs"]
22
mod backend;
3+
#[path = "../codex_config.rs"]
4+
mod codex_config;
35
#[path = "../storage.rs"]
46
mod storage;
57
#[path = "../types.rs"]
@@ -204,6 +206,7 @@ impl DaemonState {
204206
}
205207

206208
async fn update_app_settings(&self, settings: AppSettings) -> Result<AppSettings, String> {
209+
let _ = codex_config::write_steer_enabled(settings.experimental_steer_enabled);
207210
write_settings(&self.settings_path, &settings)?;
208211
let mut current = self.app_settings.lock().await;
209212
*current = settings.clone();
@@ -688,8 +691,11 @@ async fn handle_rpc_request(
688691
serde_json::to_value(files).map_err(|err| err.to_string())
689692
}
690693
"get_app_settings" => {
691-
let settings = state.app_settings.lock().await;
692-
serde_json::to_value(settings.clone()).map_err(|err| err.to_string())
694+
let mut settings = state.app_settings.lock().await.clone();
695+
if let Ok(Some(steer_enabled)) = codex_config::read_steer_enabled() {
696+
settings.experimental_steer_enabled = steer_enabled;
697+
}
698+
serde_json::to_value(settings).map_err(|err| err.to_string())
693699
}
694700
"update_app_settings" => {
695701
let settings_value = match params {

src-tauri/src/codex_config.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
use std::env;
2+
use std::fs;
3+
use std::path::PathBuf;
4+
5+
const FEATURES_TABLE: &str = "[features]";
6+
7+
pub(crate) fn read_steer_enabled() -> Result<Option<bool>, String> {
8+
read_feature_flag("steer")
9+
}
10+
11+
pub(crate) fn write_steer_enabled(enabled: bool) -> Result<(), String> {
12+
write_feature_flag("steer", enabled)
13+
}
14+
15+
fn read_feature_flag(key: &str) -> Result<Option<bool>, String> {
16+
let path = config_toml_path().ok_or("Unable to resolve CODEX_HOME".to_string())?;
17+
if !path.exists() {
18+
return Ok(None);
19+
}
20+
let contents = fs::read_to_string(&path).map_err(|err| err.to_string())?;
21+
Ok(find_feature_flag(&contents, key))
22+
}
23+
24+
fn write_feature_flag(key: &str, enabled: bool) -> Result<(), String> {
25+
let Some(path) = config_toml_path() else {
26+
return Ok(());
27+
};
28+
if let Some(parent) = path.parent() {
29+
fs::create_dir_all(parent).map_err(|err| err.to_string())?;
30+
}
31+
let contents = fs::read_to_string(&path).unwrap_or_default();
32+
let updated = upsert_feature_flag(&contents, key, enabled);
33+
fs::write(&path, updated).map_err(|err| err.to_string())
34+
}
35+
36+
fn config_toml_path() -> Option<PathBuf> {
37+
resolve_codex_home().map(|home| home.join("config.toml"))
38+
}
39+
40+
fn resolve_codex_home() -> Option<PathBuf> {
41+
if let Ok(value) = env::var("CODEX_HOME") {
42+
if !value.trim().is_empty() {
43+
return Some(PathBuf::from(value.trim()));
44+
}
45+
}
46+
resolve_home_dir().map(|home| home.join(".codex"))
47+
}
48+
49+
fn resolve_home_dir() -> Option<PathBuf> {
50+
if let Ok(value) = env::var("HOME") {
51+
if !value.trim().is_empty() {
52+
return Some(PathBuf::from(value));
53+
}
54+
}
55+
if let Ok(value) = env::var("USERPROFILE") {
56+
if !value.trim().is_empty() {
57+
return Some(PathBuf::from(value));
58+
}
59+
}
60+
None
61+
}
62+
63+
fn find_feature_flag(contents: &str, key: &str) -> Option<bool> {
64+
let mut in_features = false;
65+
for line in contents.lines() {
66+
let trimmed = line.trim();
67+
if trimmed.starts_with('[') && trimmed.ends_with(']') {
68+
in_features = trimmed == FEATURES_TABLE;
69+
continue;
70+
}
71+
if !in_features || trimmed.is_empty() || trimmed.starts_with('#') {
72+
continue;
73+
}
74+
let (candidate_key, value) = trimmed.split_once('=')?;
75+
if candidate_key.trim() != key {
76+
continue;
77+
}
78+
let value = value.split('#').next().unwrap_or("").trim();
79+
return match value {
80+
"true" => Some(true),
81+
"false" => Some(false),
82+
_ => None,
83+
};
84+
}
85+
None
86+
}
87+
88+
fn upsert_feature_flag(contents: &str, key: &str, enabled: bool) -> String {
89+
let mut lines: Vec<String> = contents.lines().map(|line| line.to_string()).collect();
90+
let mut in_features = false;
91+
let mut features_start: Option<usize> = None;
92+
let mut features_end: Option<usize> = None;
93+
let mut key_index: Option<usize> = None;
94+
95+
for (idx, line) in lines.iter().enumerate() {
96+
let trimmed = line.trim();
97+
if trimmed.starts_with('[') && trimmed.ends_with(']') {
98+
if in_features {
99+
features_end = Some(idx);
100+
break;
101+
}
102+
in_features = trimmed == FEATURES_TABLE;
103+
if in_features {
104+
features_start = Some(idx);
105+
}
106+
continue;
107+
}
108+
if !in_features || trimmed.is_empty() || trimmed.starts_with('#') {
109+
continue;
110+
}
111+
if let Some((candidate_key, _)) = trimmed.split_once('=') {
112+
if candidate_key.trim() == key {
113+
key_index = Some(idx);
114+
break;
115+
}
116+
}
117+
}
118+
119+
let flag_line = format!("{key} = {}", if enabled { "true" } else { "false" });
120+
121+
if let Some(start) = features_start {
122+
let end = features_end.unwrap_or(lines.len());
123+
if let Some(index) = key_index {
124+
lines[index] = flag_line;
125+
} else {
126+
let insert_at = if end > start + 1 { end } else { start + 1 };
127+
lines.insert(insert_at, flag_line);
128+
}
129+
} else {
130+
if !lines.is_empty() && !lines.last().unwrap().trim().is_empty() {
131+
lines.push(String::new());
132+
}
133+
lines.push(FEATURES_TABLE.to_string());
134+
lines.push(flag_line);
135+
}
136+
137+
let mut updated = lines.join("\n");
138+
if contents.ends_with('\n') || updated.is_empty() {
139+
updated.push('\n');
140+
}
141+
updated
142+
}

src-tauri/src/git.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ fn parse_pr_diff(diff: &str) -> Vec<GitHubPullRequestDiff> {
127127
let mut current_new_path: Option<String> = None;
128128
let mut current_status: Option<String> = None;
129129

130-
let mut finalize = |lines: &Vec<&str>,
130+
let finalize = |lines: &Vec<&str>,
131131
old_path: &Option<String>,
132132
new_path: &Option<String>,
133133
status: &Option<String>,

src-tauri/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use tauri::{Manager, WebviewUrl, WebviewWindowBuilder};
33

44
mod backend;
55
mod codex;
6+
mod codex_config;
67
mod dictation;
78
mod event_sink;
89
mod git;

src-tauri/src/settings.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
use tauri::State;
22

3+
use crate::codex_config;
34
use crate::state::AppState;
45
use crate::storage::write_settings;
56
use crate::types::AppSettings;
67

78
#[tauri::command]
89
pub(crate) async fn get_app_settings(state: State<'_, AppState>) -> Result<AppSettings, String> {
9-
let settings = state.app_settings.lock().await;
10-
Ok(settings.clone())
10+
let mut settings = state.app_settings.lock().await.clone();
11+
if let Ok(Some(steer_enabled)) = codex_config::read_steer_enabled() {
12+
settings.experimental_steer_enabled = steer_enabled;
13+
}
14+
Ok(settings)
1115
}
1216

1317
#[tauri::command]
1418
pub(crate) async fn update_app_settings(
1519
settings: AppSettings,
1620
state: State<'_, AppState>,
1721
) -> Result<AppSettings, String> {
22+
let _ = codex_config::write_steer_enabled(settings.experimental_steer_enabled);
1823
write_settings(&state.settings_path, &settings)?;
1924
let mut current = state.app_settings.lock().await;
2025
*current = settings.clone();

0 commit comments

Comments
 (0)