fix(provider): re-project Codex MCP after unified-session toggle rewrites live config

Toggling unify_codex_session_history rewrites the current official
provider's live config.toml in full (intended design), which drops the
[mcp_servers] projection -- and nothing put it back, so enabled MCP
servers silently vanished until the next provider switch (#C2).

Re-project after the rewrite, with two deliberate choices:

- Project Codex only (new McpService::sync_enabled_for_app) instead of
  sync_all_enabled: the all-apps sync short-circuits in AppType::all()
  order, so a corrupt ~/.claude.json would error before Codex is ever
  reached and the freshly wiped [mcp_servers] would stay missing. Only
  Codex's live file was rewritten here, so only Codex needs
  re-projection.
- Degrade projection failure to a warning: by this point the live file
  already carries the new bucket state, so the toggle has taken effect.
  Propagating the error would make save_settings roll back the setting,
  creating the exact "setting=old, live=new bucket" session split the
  rollback exists to prevent. The projection self-heals on the next
  switch or any MCP toggle.
This commit is contained in:
Jason
2026-07-08 16:29:37 +08:00
parent 473c2aaa9f
commit 6d2ee2472f
4 changed files with 238 additions and 9 deletions
+1
View File
@@ -55,6 +55,7 @@ pub use prompt::Prompt;
pub use provider::{Provider, ProviderMeta};
pub use services::{
profile::{ProfilePayload, ProfileScope, ProfileService},
provider::reapply_current_codex_official_live,
skill::{migrate_skills_to_ssot, ImportSkillSelection},
ConfigService, EndpointLatency, McpService, PromptService, ProviderService, ProxyService,
SkillService, SpeedtestService,
+28 -9
View File
@@ -181,16 +181,35 @@ impl McpService {
let servers = Self::get_all_servers(state)?;
for app in AppType::all() {
if matches!(app, AppType::OpenClaw | AppType::ClaudeDesktop) {
continue;
}
Self::project_servers_to_app(state, &servers, &app)?;
}
for server in servers.values() {
if server.apps.is_enabled_for(&app) {
Self::sync_server_to_app(state, server, &app)?;
} else {
Self::remove_server_from_app(state, &server.id, &app)?;
}
Ok(())
}
/// 只把启用状态投影到单个应用。某个应用的 live 被整体重写后用它做
/// 定向重投影:sync_all_enabled 按 AppType::all() 顺序逐应用短路,
/// 排在前面的无关应用 live 损坏(如 ~/.claude.json 坏 JSON)会阻断
/// 后面应用的投影。
pub fn sync_enabled_for_app(state: &AppState, app: &AppType) -> Result<(), AppError> {
let servers = Self::get_all_servers(state)?;
Self::project_servers_to_app(state, &servers, app)
}
fn project_servers_to_app(
state: &AppState,
servers: &IndexMap<String, McpServer>,
app: &AppType,
) -> Result<(), AppError> {
if matches!(app, AppType::OpenClaw | AppType::ClaudeDesktop) {
return Ok(());
}
for server in servers.values() {
if server.apps.is_enabled_for(app) {
Self::sync_server_to_app(state, server, app)?;
} else {
Self::remove_server_from_app(state, &server.id, app)?;
}
}
+12
View File
@@ -81,6 +81,18 @@ pub fn reapply_current_codex_official_live(state: &AppState) -> Result<bool, App
}
live::write_live_with_common_config(&state.db, &AppType::Codex, provider)?;
// 重写 live 会整体替换 config.toml(有意设计),[mcp_servers] 随之丢失,
// 写完必须立刻从 DB 重新投影启用的 MCP。只投影 Codex 而非
// sync_all_enabled:后者按 AppType::all() 顺序逐应用短路,排在 Codex
// 前面的无关应用 live 损坏(如 ~/.claude.json 坏 JSON)会阻断 Codex
// 的重投影,让刚被清掉的 [mcp_servers] 无人补回。
// 投影失败降级为警告:走到这里 live 已按新开关状态落盘,开关事实上
// 已生效;若把错误上抛,save_settings 会回滚开关设置,制造"设置=旧值、
// live=新桶"的会话分裂——正是该回滚要防止的状态。MCP 投影可自愈
// (下次切换 / 任一 MCP 启停操作都会重新投影)。
if let Err(err) = McpService::sync_enabled_for_app(state, &AppType::Codex) {
log::warn!("统一会话开关重写 live 后重投影 Codex MCP 失败(将在下次同步时自愈): {err}");
}
Ok(true)
}