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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ venv/
build/
dist/
target/
node_modules/
apps/codex-plus-manager/src-tauri/gen/

.codex_asar_extract/
Expand Down
4 changes: 1 addition & 3 deletions apps/codex-plus-launcher/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ fn acquire_single_instance_guard_with_retry(
}

fn try_acquire_single_instance_guard() -> std::io::Result<std::net::TcpListener> {
codex_plus_core::ports::acquire_loopback_port_guard(
codex_plus_core::ports::LAUNCHER_GUARD_PORT,
)
codex_plus_core::ports::acquire_loopback_port_guard(codex_plus_core::ports::LAUNCHER_GUARD_PORT)
}

fn should_recover_stale_launcher(debug_port: u16) -> bool {
Expand Down
93 changes: 85 additions & 8 deletions apps/codex-plus-manager/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,10 +713,14 @@ fn strip_common_config_text_fallback(config_contents: &str, common_config: &str)

let mut kept = Vec::new();
let mut skipping_table = false;
let mut in_root_section = true;
let mut removed_root_keys = std::collections::HashSet::new();
let source_root_keys = toml_root_keys_before_first_table(config_contents);

for line in config_contents.lines() {
let trimmed = line.trim();
if trimmed.starts_with('[') && trimmed.ends_with(']') {
in_root_section = false;
let header = trimmed.to_string();
skipping_table = common.table_headers.contains(&header);
if skipping_table {
Expand All @@ -728,9 +732,21 @@ fn strip_common_config_text_fallback(config_contents: &str, common_config: &str)
continue;
}

if let Some(key) = toml_key_from_line(trimmed) {
if in_root_section && let Some(key) = toml_key_from_line(trimmed) {
if common.root_keys.contains(key) {
continue;
let is_duplicate_common_key = removed_root_keys.contains(key)
|| source_root_keys.contains(key)
|| common.table_headers.contains("[features]")
|| common
.table_headers
.contains("[marketplaces.openai-bundled]")
|| common
.table_headers
.contains("[plugins.\"superpowers@openai-curated\"]");
if is_duplicate_common_key {
removed_root_keys.insert(key.to_string());
continue;
}
}
}

Expand All @@ -740,6 +756,20 @@ fn strip_common_config_text_fallback(config_contents: &str, common_config: &str)
ensure_text_newline(kept.join("\n").trim_end())
}

fn toml_root_keys_before_first_table(config_contents: &str) -> std::collections::HashSet<String> {
let mut keys = std::collections::HashSet::new();
for line in config_contents.lines() {
let trimmed = line.trim();
if trimmed.starts_with('[') && trimmed.ends_with(']') {
break;
}
if let Some(key) = toml_key_from_line(trimmed) {
keys.insert(key.to_string());
}
}
keys
}

struct CommonConfigAnchors {
root_keys: std::collections::HashSet<String>,
table_headers: std::collections::HashSet<String>,
Expand Down Expand Up @@ -1534,6 +1564,9 @@ pub fn apply_relay_injection() -> CommandResult<RelayPayload> {
}
let relay = settings.active_relay_profile();
log_relay_apply_request("manager.apply_relay_injection", &settings, &relay);
if settings.active_aggregate_relay_profile().is_some() {
return apply_aggregate_relay_injection_to_home(&home);
}
if relay_has_complete_files(&relay) {
return match codex_plus_core::relay_config::apply_relay_profile_to_home_with_switch_rules(
&home,
Expand Down Expand Up @@ -1625,6 +1658,33 @@ pub fn apply_relay_injection() -> CommandResult<RelayPayload> {
}
}

fn apply_aggregate_relay_injection_to_home(home: &Path) -> CommandResult<RelayPayload> {
match codex_plus_core::relay_config::apply_relay_config_to_home_with_protocol(
home,
&codex_plus_core::protocol_proxy::local_responses_proxy_base_url(
codex_plus_core::protocol_proxy::DEFAULT_PROTOCOL_PROXY_PORT,
),
"codex-plus-aggregate",
codex_plus_core::settings::RelayProtocol::Responses,
codex_plus_core::protocol_proxy::DEFAULT_PROTOCOL_PROXY_PORT,
) {
Ok(result) => {
let status = codex_plus_core::relay_config::relay_status_from_home(home);
ok(
"聚合供应商配置已写入,真实请求会由本地代理按策略轮转。",
relay_payload(status, result.backup_path),
)
}
Err(error) => {
let status = codex_plus_core::relay_config::relay_status_from_home(home);
failed(
&format!("写入聚合供应商配置失败:{error}"),
relay_payload(status, None),
)
}
}
}

#[tauri::command]
pub fn apply_pure_api_injection() -> CommandResult<RelayPayload> {
let home = codex_plus_core::relay_config::default_codex_home_dir();
Expand Down Expand Up @@ -2358,6 +2418,20 @@ mod tests {
assert!(text.contains("hasBearerToken"));
}

#[test]
fn aggregate_relay_injection_writes_local_proxy_without_chatgpt_auth() {
let temp = tempfile::tempdir().unwrap();

let result = apply_aggregate_relay_injection_to_home(temp.path());
let config = std::fs::read_to_string(temp.path().join("config.toml")).unwrap();

assert_eq!(result.status, "ok");
assert!(result.payload.configured);
assert!(!result.payload.authenticated);
assert!(config.contains(r#"base_url = "http://127.0.0.1:57321/v1""#));
assert!(config.contains(r#"experimental_bearer_token = "codex-plus-aggregate""#));
}

#[test]
fn relay_files_payload_reads_config_and_auth_contents() {
let temp = tempfile::tempdir().unwrap();
Expand Down Expand Up @@ -2429,7 +2503,6 @@ mod tests {
relay_common_config_contents: "[mcp_servers.context7]\ncommand = \"npx\"\n".to_string(),
relay_profiles: vec![RelayProfile {
use_common_config: false,
relay_mode: codex_plus_core::settings::RelayMode::PureApi,
config_contents: "model = \"gpt-5\"\n\n[mcp_servers.context7]\ncommand = \"npx\"\n"
.to_string(),
..RelayProfile::default()
Expand Down Expand Up @@ -2477,10 +2550,16 @@ mod tests {

let normalized = normalize_settings_before_save(settings);

let auth_json: serde_json::Value =
serde_json::from_str(&normalized.relay_profiles[0].auth_contents).unwrap();
assert_eq!(
serde_json::from_str::<serde_json::Value>(&normalized.relay_profiles[0].auth_contents)
.unwrap(),
serde_json::json!({"auth_mode":"chatgpt","tokens":{"access_token":"edited"}})
auth_json,
serde_json::json!({
"auth_mode": "chatgpt",
"tokens": {
"access_token": "edited"
}
})
);
assert!(normalized.relay_profiles[0].config_contents.is_empty());
}
Expand Down Expand Up @@ -2527,7 +2606,6 @@ enabled = true
.to_string(),
relay_profiles: vec![RelayProfile {
use_common_config: true,
relay_mode: codex_plus_core::settings::RelayMode::PureApi,
config_contents: r#"model = "gpt-5"
model_reasoning_effort = "high"

Expand Down Expand Up @@ -2564,7 +2642,6 @@ last_updated = "2026-05-25T11:52:46Z"
.to_string(),
relay_profiles: vec![RelayProfile {
use_common_config: true,
relay_mode: codex_plus_core::settings::RelayMode::PureApi,
config_contents: r#"model = "gpt-5"
model_reasoning_effort = "high"

Expand Down
6 changes: 6 additions & 0 deletions apps/codex-plus-manager/src-tauri/tests/windows_subsystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ fn relay_settings_keeps_profile_config_and_auth_files_isolated() {
assert!(app_tsx.contains("relayProfileSwitchValidation(selectedBeforeSave)"));
assert!(app_tsx.contains("缺少独立 config.toml"));
assert!(app_tsx.contains("const command = relayProfileSwitchCommand(selectedAfterSave)"));
assert!(app_tsx.contains("function relayProfileSwitchCommand"));
assert!(app_tsx.contains("return \"apply_pure_api_injection\""));
assert!(app_tsx.contains("return \"apply_relay_injection\""));
assert!(app_tsx.contains("const createNewAggregateProfile = () =>"));
assert!(app_tsx.contains("onClick={createNewAggregateProfile}"));
assert!(app_tsx.contains("已打开聚合供应商详情"));
assert!(!commands_rs.contains("缺少独立 auth.json"));
assert!(commands_rs.contains("backfill_relay_profile_from_live"));
assert!(commands_rs.contains("apply_relay_profile_to_home_with_switch_rules"));
Expand Down
Loading
Loading