Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions apps/src/app/settings/components/general-basics-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { AppSettings } from "@/types";
type GeneralBasicsSnapshot = Pick<
AppSettings,
| "updateAutoCheck"
| "removeRequiresOpenaiAuth"
| "autoStartEnabled"
| "autoStartSupported"
| "showMainWindowOnStartup"
Expand Down Expand Up @@ -143,6 +144,22 @@ export function GeneralBasicsCard({
}
/>
</div>
<div className="flex items-center justify-between gap-4">
<div className="space-y-0.5">
<Label>{t("移除 requires_openai_auth")}</Label>
<p className="text-xs text-muted-foreground">
{t(
"开启后,在切换到 CodexManager 网关或同步配置时移除 requires_openai_auth;关闭时保留现有配置,默认关闭",
)}
</p>
</div>
<Switch
checked={snapshot.removeRequiresOpenaiAuth}
onCheckedChange={(value) =>
updateSettings.mutate({ removeRequiresOpenaiAuth: value })
}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>{t("开机自动启动")}</Label>
Expand Down
1 change: 1 addition & 0 deletions apps/src/lib/api/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1812,6 +1812,7 @@ export function normalizeAppSettings(payload: unknown): AppSettings {
);
return {
updateAutoCheck: asBoolean(source.updateAutoCheck, true),
removeRequiresOpenaiAuth: asBoolean(source.removeRequiresOpenaiAuth, false),
autoStartEnabled: asBoolean(source.autoStartEnabled, false),
autoStartSupported: asBoolean(source.autoStartSupported, false),
showMainWindowOnStartup: asBoolean(source.showMainWindowOnStartup, true),
Expand Down
3 changes: 3 additions & 0 deletions apps/src/lib/i18n/messages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ export const EN_MESSAGES: MessageCatalog = {
基础设置: "Basic settings",
控制应用启动和窗口行为: "Control startup and window behavior.",
自动检查更新: "Check updates automatically",
"移除 requires_openai_auth": "Remove requires_openai_auth",
"开启后,在切换到 CodexManager 网关或同步配置时移除 requires_openai_auth;关闭时保留现有配置,默认关闭":
"When enabled, remove requires_openai_auth while switching to the CodexManager gateway or syncing the profile; when disabled, keep the existing configuration. Disabled by default.",
"启动完成后在后台检查更新,并每 7 小时检查一次":
"Check for updates in the background after startup, then every 7 hours.",
"检测到新版本,是否现在更新?":
Expand Down
3 changes: 3 additions & 0 deletions apps/src/lib/i18n/messages/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ export const KO_MESSAGES: MessageCatalog = {
"CodexManager는 Codex CLI 계정, 로컬 게이트웨이, 플랫폼 키, 요청 로그, 사용량 통계를 통합 관리하여 Codex 연결과 운영을 더 집중적이고 제어 가능하게 합니다.",
基础设置: "기본 설정",
自动检查更新: "자동 업데이트 확인",
"移除 requires_openai_auth": "requires_openai_auth 제거",
"开启后,在切换到 CodexManager 网关或同步配置时移除 requires_openai_auth;关闭时保留现有配置,默认关闭":
"켜면 CodexManager 게이트웨이로 전환하거나 프로필을 동기화할 때 requires_openai_auth를 제거하고, 끄면 기존 설정을 유지합니다. 기본값은 꺼짐입니다.",
"启动完成后在后台检查更新,并每 7 小时检查一次":
"시작이 완료되면 백그라운드에서 업데이트를 확인하고 이후 7시간마다 확인합니다.",
"检测到新版本,是否现在更新?":
Expand Down
3 changes: 3 additions & 0 deletions apps/src/lib/i18n/messages/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ export const RU_MESSAGES: MessageCatalog = {
基础设置: "Основные настройки",
控制应用启动和窗口行为: "Управление запуском приложения и поведением окна.",
自动检查更新: "Автопроверка обновлений",
"移除 requires_openai_auth": "Удалять requires_openai_auth",
"开启后,在切换到 CodexManager 网关或同步配置时移除 requires_openai_auth;关闭时保留现有配置,默认关闭":
"Если включено, удалять requires_openai_auth при переключении на шлюз CodexManager или синхронизации профиля; если выключено, сохранять текущую конфигурацию. По умолчанию выключено.",
"启动完成后在后台检查更新,并每 7 小时检查一次":
"Проверять обновления в фоне после запуска, а затем каждые 7 часов.",
"检测到新版本,是否现在更新?":
Expand Down
1 change: 1 addition & 0 deletions apps/src/lib/store/useAppStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const useAppStore = create<AppState>((set) => ({
},
appSettings: {
updateAutoCheck: true,
removeRequiresOpenaiAuth: false,
autoStartEnabled: false,
autoStartSupported: false,
showMainWindowOnStartup: true,
Expand Down
1 change: 1 addition & 0 deletions apps/src/types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export type GatewayTransportPatch = Partial<GatewayTransportValues>;

export interface AppSettings extends GatewayTransportValues {
updateAutoCheck: boolean;
removeRequiresOpenaiAuth: boolean;
autoStartEnabled: boolean;
autoStartSupported: boolean;
showMainWindowOnStartup: boolean;
Expand Down
14 changes: 12 additions & 2 deletions crates/service/src/app_settings/api/current.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ use super::{
save_persisted_bool_setting, sync_runtime_settings_from_storage,
APP_SETTING_AUTHOR_SERVER_RECOMMENDATIONS_KEY, APP_SETTING_AUTHOR_SPONSORS_KEY,
APP_SETTING_AUTO_START_ENABLED_KEY, APP_SETTING_CLOSE_TO_TRAY_ON_CLOSE_KEY,
APP_SETTING_ENV_OVERRIDES_KEY, APP_SETTING_GATEWAY_ACCOUNT_MAX_INFLIGHT_KEY,
APP_SETTING_GATEWAY_BACKGROUND_TASKS_KEY, APP_SETTING_GATEWAY_COMPACT_MODEL_FORWARD_RULES_KEY,
APP_SETTING_CODEX_PROFILE_REMOVE_REQUIRES_OPENAI_AUTH_KEY, APP_SETTING_ENV_OVERRIDES_KEY,
APP_SETTING_GATEWAY_ACCOUNT_MAX_INFLIGHT_KEY, APP_SETTING_GATEWAY_BACKGROUND_TASKS_KEY,
APP_SETTING_GATEWAY_COMPACT_MODEL_FORWARD_RULES_KEY,
APP_SETTING_GATEWAY_FREE_ACCOUNT_MAX_MODEL_KEY, APP_SETTING_GATEWAY_MODEL_FORWARD_RULES_KEY,
APP_SETTING_GATEWAY_ORIGINATOR_KEY, APP_SETTING_GATEWAY_QUOTA_GUARD_KEY,
APP_SETTING_GATEWAY_RESIDENCY_REQUIREMENT_KEY, APP_SETTING_GATEWAY_ROUTE_STRATEGY_KEY,
Expand Down Expand Up @@ -163,6 +164,11 @@ fn current_app_settings_value_inner(
let runtime_time_zone = current_runtime_time_zone_value();
let update_auto_check = setting_bool(&settings, APP_SETTING_UPDATE_AUTO_CHECK_KEY, true);
let auto_start_enabled = setting_bool(&settings, APP_SETTING_AUTO_START_ENABLED_KEY, false);
let remove_requires_openai_auth = setting_bool(
&settings,
APP_SETTING_CODEX_PROFILE_REMOVE_REQUIRES_OPENAI_AUTH_KEY,
false,
);
let show_main_window_on_startup =
setting_bool(&settings, APP_SETTING_SHOW_MAIN_WINDOW_ON_STARTUP_KEY, true);
let persisted_close_to_tray =
Expand Down Expand Up @@ -391,6 +397,10 @@ fn current_app_settings_value_inner(
"webAccessPasswordConfigured": web_access_password_configured(),
});
if let Some(object) = result.as_object_mut() {
object.insert(
"removeRequiresOpenaiAuth".to_string(),
remove_requires_openai_auth.into(),
);
object.insert("zoomFactor".to_string(), zoom_factor.into());
object.insert("gatewayUserAgent".to_string(), gateway_user_agent.into());
object.insert(
Expand Down
5 changes: 3 additions & 2 deletions crates/service/src/app_settings/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ pub(super) use super::{normalize_optional_text, parse_bool_with_default};
pub(super) use super::{
APP_SETTING_AUTHOR_SERVER_RECOMMENDATIONS_KEY, APP_SETTING_AUTHOR_SPONSORS_KEY,
APP_SETTING_AUTO_START_ENABLED_KEY, APP_SETTING_CLOSE_TO_TRAY_ON_CLOSE_KEY,
APP_SETTING_ENV_OVERRIDES_KEY, APP_SETTING_GATEWAY_ACCOUNT_MAX_INFLIGHT_KEY,
APP_SETTING_GATEWAY_BACKGROUND_TASKS_KEY, APP_SETTING_GATEWAY_COMPACT_MODEL_FORWARD_RULES_KEY,
APP_SETTING_CODEX_PROFILE_REMOVE_REQUIRES_OPENAI_AUTH_KEY, APP_SETTING_ENV_OVERRIDES_KEY,
APP_SETTING_GATEWAY_ACCOUNT_MAX_INFLIGHT_KEY, APP_SETTING_GATEWAY_BACKGROUND_TASKS_KEY,
APP_SETTING_GATEWAY_COMPACT_MODEL_FORWARD_RULES_KEY,
APP_SETTING_GATEWAY_FREE_ACCOUNT_MAX_MODEL_KEY, APP_SETTING_GATEWAY_MODEL_FORWARD_RULES_KEY,
APP_SETTING_GATEWAY_ORIGINATOR_KEY, APP_SETTING_GATEWAY_QUOTA_GUARD_KEY,
APP_SETTING_GATEWAY_RESIDENCY_REQUIREMENT_KEY, APP_SETTING_GATEWAY_ROUTE_STRATEGY_KEY,
Expand Down
21 changes: 15 additions & 6 deletions crates/service/src/app_settings/api/patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ use super::author_links::{
normalize_author_link_items, serialize_author_link_items, AuthorLinkItem,
};
use super::{
save_persisted_app_setting, set_auto_start_enabled_setting, set_close_to_tray_on_close_setting,
set_codex_cli_guide_dismissed, set_env_overrides, set_gateway_account_max_inflight,
set_gateway_background_tasks, set_gateway_compact_model_forward_rules,
set_gateway_free_account_max_model, set_gateway_model_forward_rules, set_gateway_originator,
set_gateway_quota_guard, set_gateway_residency_requirement, set_gateway_route_strategy,
save_persisted_app_setting, save_persisted_bool_setting, set_auto_start_enabled_setting,
set_close_to_tray_on_close_setting, set_codex_cli_guide_dismissed, set_env_overrides,
set_gateway_account_max_inflight, set_gateway_background_tasks,
set_gateway_compact_model_forward_rules, set_gateway_free_account_max_model,
set_gateway_model_forward_rules, set_gateway_originator, set_gateway_quota_guard,
set_gateway_residency_requirement, set_gateway_route_strategy,
set_gateway_sse_keepalive_enabled, set_gateway_sse_keepalive_interval_ms,
set_gateway_thread_aware_account_distribution_enabled, set_gateway_upstream_proxy_bypass_hosts,
set_gateway_upstream_proxy_url, set_gateway_upstream_stream_timeout_ms,
Expand All @@ -20,13 +21,15 @@ use super::{
set_ui_appearance_preset, set_ui_locale, set_ui_low_transparency_enabled, set_ui_theme,
set_ui_zoom_factor, set_update_auto_check_enabled, BackgroundTasksInput, QuotaGuardInput,
APP_SETTING_AUTHOR_SERVER_RECOMMENDATIONS_KEY, APP_SETTING_AUTHOR_SPONSORS_KEY,
APP_SETTING_PLUGIN_MARKET_MODE_KEY, APP_SETTING_PLUGIN_MARKET_SOURCE_URL_KEY,
APP_SETTING_CODEX_PROFILE_REMOVE_REQUIRES_OPENAI_AUTH_KEY, APP_SETTING_PLUGIN_MARKET_MODE_KEY,
APP_SETTING_PLUGIN_MARKET_SOURCE_URL_KEY,
};

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(super) struct AppSettingsPatch {
update_auto_check: Option<bool>,
remove_requires_openai_auth: Option<bool>,
auto_start_enabled: Option<bool>,
show_main_window_on_startup: Option<bool>,
close_to_tray_on_close: Option<bool>,
Expand Down Expand Up @@ -102,6 +105,12 @@ pub(super) fn apply_app_settings_patch(patch: AppSettingsPatch) -> Result<(), St
if let Some(enabled) = patch.update_auto_check {
set_update_auto_check_enabled(enabled)?;
}
if let Some(enabled) = patch.remove_requires_openai_auth {
save_persisted_bool_setting(
APP_SETTING_CODEX_PROFILE_REMOVE_REQUIRES_OPENAI_AUTH_KEY,
enabled,
)?;
}
if let Some(enabled) = patch.auto_start_enabled {
set_auto_start_enabled_setting(enabled)?;
}
Expand Down
1 change: 1 addition & 0 deletions crates/service/src/app_settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub(crate) use shared::{normalize_optional_text, parse_bool_with_default};
pub use shared::{
APP_SETTING_AUTHOR_SERVER_RECOMMENDATIONS_KEY, APP_SETTING_AUTHOR_SPONSORS_KEY,
APP_SETTING_AUTO_START_ENABLED_KEY, APP_SETTING_CLOSE_TO_TRAY_ON_CLOSE_KEY,
APP_SETTING_CODEX_PROFILE_REMOVE_REQUIRES_OPENAI_AUTH_KEY,
APP_SETTING_DISTRIBUTION_ENABLED_KEY, APP_SETTING_ENV_OVERRIDES_KEY,
APP_SETTING_GATEWAY_ACCOUNT_MAX_INFLIGHT_KEY, APP_SETTING_GATEWAY_BACKGROUND_TASKS_KEY,
APP_SETTING_GATEWAY_COMPACT_MODEL_FORWARD_RULES_KEY,
Expand Down
2 changes: 2 additions & 0 deletions crates/service/src/app_settings/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ pub const APP_SETTING_UI_CODEX_CLI_GUIDE_DISMISSED_KEY: &str = "ui.codex_cli_gui
pub const APP_SETTING_UI_THEME_KEY: &str = "ui.theme";
pub const APP_SETTING_UI_APPEARANCE_PRESET_KEY: &str = "ui.appearance_preset";
pub const APP_SETTING_UI_LOCALE_KEY: &str = "ui.locale";
pub const APP_SETTING_CODEX_PROFILE_REMOVE_REQUIRES_OPENAI_AUTH_KEY: &str =
"codex_profile.remove_requires_openai_auth";
pub const APP_SETTING_SERVICE_ADDR_KEY: &str = "app.service_addr";
pub const APP_SETTING_AUTHOR_SPONSORS_KEY: &str = "author.sponsors";
pub const APP_SETTING_AUTHOR_SERVER_RECOMMENDATIONS_KEY: &str = "author.server_recommendations";
Expand Down
18 changes: 16 additions & 2 deletions crates/service/src/codex_profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ pub(crate) fn apply_gateway(
&paths.gateway_model_catalog_path,
supports_websockets,
&secret,
remove_requires_openai_auth_enabled(),
)?;
write_profile_files(
&profile_dir,
Expand Down Expand Up @@ -1931,6 +1932,14 @@ fn expand_home_prefix(input: &str) -> PathBuf {
PathBuf::from(input)
}

fn remove_requires_openai_auth_enabled() -> bool {
crate::app_settings::get_persisted_app_setting(
crate::app_settings::APP_SETTING_CODEX_PROFILE_REMOVE_REQUIRES_OPENAI_AUTH_KEY,
)
.map(|value| crate::app_settings::parse_bool_with_default(&value, false))
.unwrap_or(false)
}

fn profile_key(profile_dir: &Path) -> String {
profile_dir.to_string_lossy().to_string()
}
Expand Down Expand Up @@ -2041,6 +2050,7 @@ fn patch_config_for_gateway(
managed_catalog_path: &Path,
supports_websockets: bool,
bearer_token: &str,
remove_requires_openai_auth: bool,
) -> Result<String, String> {
let mut doc = parse_config(content.as_deref().unwrap_or(""))?;
doc.as_table_mut()
Expand Down Expand Up @@ -2076,8 +2086,11 @@ fn patch_config_for_gateway(
// Codex only treats actor authorization as an extension capability for custom providers
// that do not use ambient OpenAI auth. A provider-scoped bearer token works for local and
// remote CodexManager gateways without coupling the profile to a movable desktop executable.
// It is mutually exclusive with command/env/ambient auth, so remove stale values first.
provider.remove("requires_openai_auth");
// Keep the user's existing requires_openai_auth value unless the opt-in setting asks us to
// remove it.
if remove_requires_openai_auth {
provider.remove("requires_openai_auth");
}
set_provider_bearer_auth(provider, bearer_token)?;
provider.insert("base_url", toml_value(base_url));
provider.insert("wire_api", toml_value("responses"));
Expand Down Expand Up @@ -2162,6 +2175,7 @@ pub(crate) fn sync_active_gateway_profile_from_storage(storage: &Storage) -> Res
&paths.gateway_model_catalog_path,
supports_websockets,
secret,
remove_requires_openai_auth_enabled(),
)?;
write_atomic(&profile_dir.join(CONFIG_FILE), &config_toml)?;
if state.supports_websockets != Some(supports_websockets) {
Expand Down
37 changes: 37 additions & 0 deletions crates/service/src/codex_profile_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ name = "Other"
&managed_catalog,
true,
"cm-managed-key",
true,
)
.expect("patch gateway");

Expand Down Expand Up @@ -220,11 +221,45 @@ name = "Other"
&managed_catalog,
false,
"cm-managed-key",
true,
)
.expect("disable gateway websocket");
assert!(without_websocket.contains("supports_websockets = false"));
}

#[test]
fn gateway_config_preserves_requires_openai_auth_when_removal_is_disabled() {
let input = r#"
model_provider = "cm"

[model_providers.cm]
name = "Custom Gateway"
requires_openai_auth = true
"#;

let output = patch_config_for_gateway(
Some(input.to_string()),
"http://127.0.0.1:48770/v1",
Path::new("/tmp/gateway-models.json"),
false,
"cm-managed-key",
false,
)
.expect("patch gateway without removing auth requirement");
let doc = parse_config(&output).expect("parse patched gateway config");
let provider = doc
.get("model_providers")
.and_then(Item::as_table)
.and_then(|providers| providers.get(PROVIDER_ID))
.and_then(Item::as_table)
.expect("managed provider");

assert_eq!(
provider.get("requires_openai_auth").and_then(Item::as_bool),
Some(true)
);
}

#[test]
fn gateway_config_preserves_custom_managed_provider_values() {
let input = r#"
Expand All @@ -248,6 +283,7 @@ http_headers = { "x-existing-header" = "keep", "x-openai-actor-authorization" =
&managed_catalog,
true,
"cm-managed-key",
true,
)
.expect("patch gateway");
let doc = parse_config(&output).expect("parse patched gateway config");
Expand Down Expand Up @@ -336,6 +372,7 @@ fn invalid_toml_is_rejected() {
Path::new("/tmp/gateway-models.json"),
false,
"cm-managed-key",
false,
)
.is_err());
}
Expand Down
12 changes: 7 additions & 5 deletions crates/service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,13 @@ pub use app_settings::{
set_show_main_window_on_startup_setting, set_ui_appearance_preset,
set_ui_low_transparency_enabled, set_ui_theme, set_update_auto_check_enabled,
sync_runtime_settings_from_storage, BackgroundTasksInput, APP_SETTING_AUTO_START_ENABLED_KEY,
APP_SETTING_CLOSE_TO_TRAY_ON_CLOSE_KEY, APP_SETTING_DISTRIBUTION_ENABLED_KEY,
APP_SETTING_ENV_OVERRIDES_KEY, APP_SETTING_GATEWAY_ACCOUNT_MAX_INFLIGHT_KEY,
APP_SETTING_GATEWAY_BACKGROUND_TASKS_KEY, APP_SETTING_GATEWAY_FREE_ACCOUNT_MAX_MODEL_KEY,
APP_SETTING_GATEWAY_MODEL_FORWARD_RULES_KEY, APP_SETTING_GATEWAY_ORIGINATOR_KEY,
APP_SETTING_GATEWAY_QUOTA_GUARD_KEY, APP_SETTING_GATEWAY_REQUEST_COMPRESSION_ENABLED_KEY,
APP_SETTING_CLOSE_TO_TRAY_ON_CLOSE_KEY,
APP_SETTING_CODEX_PROFILE_REMOVE_REQUIRES_OPENAI_AUTH_KEY,
APP_SETTING_DISTRIBUTION_ENABLED_KEY, APP_SETTING_ENV_OVERRIDES_KEY,
APP_SETTING_GATEWAY_ACCOUNT_MAX_INFLIGHT_KEY, APP_SETTING_GATEWAY_BACKGROUND_TASKS_KEY,
APP_SETTING_GATEWAY_FREE_ACCOUNT_MAX_MODEL_KEY, APP_SETTING_GATEWAY_MODEL_FORWARD_RULES_KEY,
APP_SETTING_GATEWAY_ORIGINATOR_KEY, APP_SETTING_GATEWAY_QUOTA_GUARD_KEY,
APP_SETTING_GATEWAY_REQUEST_COMPRESSION_ENABLED_KEY,
APP_SETTING_GATEWAY_RESIDENCY_REQUIREMENT_KEY, APP_SETTING_GATEWAY_ROUTE_STRATEGY_KEY,
APP_SETTING_GATEWAY_SSE_KEEPALIVE_ENABLED_KEY,
APP_SETTING_GATEWAY_SSE_KEEPALIVE_INTERVAL_MS_KEY,
Expand Down
42 changes: 42 additions & 0 deletions crates/service/tests/app_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,48 @@ fn app_settings_roundtrip_gateway_user_agent_and_validates_header_value() {
});
}

#[test]
fn app_settings_remove_requires_openai_auth_defaults_off_and_persists_updates() {
with_temp_db(|db_path| {
let defaults = codexmanager_service::app_settings_get().expect("read default settings");
assert_eq!(defaults["removeRequiresOpenaiAuth"], false);
let storage = Storage::open(db_path).expect("open storage");
assert_eq!(
storage
.get_app_setting(
codexmanager_service::APP_SETTING_CODEX_PROFILE_REMOVE_REQUIRES_OPENAI_AUTH_KEY
)
.expect("read default persisted setting"),
None
);
drop(storage);

let enabled = codexmanager_service::app_settings_set(Some(&json!({
"removeRequiresOpenaiAuth": true
})))
.expect("enable requires_openai_auth removal");
assert_eq!(enabled["removeRequiresOpenaiAuth"], true);

let storage = Storage::open(db_path).expect("open storage");
assert_eq!(
storage
.get_app_setting(
codexmanager_service::APP_SETTING_CODEX_PROFILE_REMOVE_REQUIRES_OPENAI_AUTH_KEY
)
.expect("read persisted setting")
.as_deref(),
Some("1")
);
drop(storage);

let disabled = codexmanager_service::app_settings_set(Some(&json!({
"removeRequiresOpenaiAuth": false
})))
.expect("disable requires_openai_auth removal");
assert_eq!(disabled["removeRequiresOpenaiAuth"], false);
});
}

#[test]
fn app_settings_rejects_password_mode_without_password() {
with_temp_db(|_| {
Expand Down