feat(profiles): split Claude Desktop into independent profile scope

This commit is contained in:
Jason
2026-07-04 23:18:20 +08:00
parent 3ec83578f6
commit 754af2cc31
11 changed files with 137 additions and 43 deletions
+73 -11
View File
@@ -174,7 +174,7 @@ fn profile_snapshot_apply_roundtrip_restores_configuration() {
.save_prompt(AppType::Claude.as_str(), &prompt("pr2", false))
.expect("save prompt pr2");
// ---- 保存项目 A(在 Claude 页新建:只拍 Claude + Desktop 当前状态)----
// ---- 保存项目 A(在 Claude 页新建:只拍 Claude 当前状态)----
let profile_a = ProfileService::create(&state, "Project A", ProfileScope::Claude)
.expect("create profile A");
let payload: ProfilePayload =
@@ -191,18 +191,14 @@ fn profile_snapshot_apply_roundtrip_restores_configuration() {
"codex side not captured when creating from the claude group"
);
assert_eq!(payload.mcp.codex, None, "uncaptured side stays None");
assert_eq!(payload.providers.claude_desktop.as_deref(), Some("d1"));
assert_eq!(
(payload.mcp.claude_desktop, payload.skills.claude_desktop),
(Some(vec![]), Some(vec![])),
"desktop has no MCP/Skills dimension (captured as empty)"
payload.providers.claude_desktop, None,
"claude desktop has its own profile scope"
);
assert_eq!(payload.prompts.claude_desktop, None);
// ---- 改动全部四类配置(走真实切换路径)----
ProviderService::switch(&state, AppType::Claude, "p2").expect("switch to p2");
// Desktop live 写入仅 macOS/Windows 可用;其他平台不动 d1,
// apply 时命中幂等跳过(这正是 Linux 上带 Desktop 槽的 payload 应有行为)
// Desktop 现在有自己的项目分组;Claude 分组 apply 不应再影响 Desktop
#[cfg(any(target_os = "macos", windows))]
ProviderService::switch(&state, AppType::ClaudeDesktop, "d2").expect("switch desktop to d2");
McpService::toggle_app(&state, "m1", AppType::Claude, false).expect("disable m1");
@@ -211,7 +207,7 @@ fn profile_snapshot_apply_roundtrip_restores_configuration() {
.expect("disable skill");
PromptService::enable_prompt(&state, AppType::Claude, "pr2").expect("enable pr2");
// ---- 应用项目 AClaude 组):全部复原 ----
// ---- 应用项目 AClaude 组):只复原 Claude 侧 ----
let (warnings, _) = ProfileService::apply(&state, &profile_a.id, ProfileScope::Claude)
.expect("apply profile A");
assert!(warnings.is_empty(), "unexpected warnings: {warnings:?}");
@@ -222,14 +218,15 @@ fn profile_snapshot_apply_roundtrip_restores_configuration() {
.expect("get current provider");
assert_eq!(current.as_deref(), Some("p1"), "provider restored to p1");
// Claude 分组不再管理 DesktopDesktop 保持 d2 不变
let current_desktop = state
.db
.get_current_provider(AppType::ClaudeDesktop.as_str())
.expect("get current desktop provider");
assert_eq!(
current_desktop.as_deref(),
Some("d1"),
"desktop provider restored to d1"
Some("d2"),
"desktop provider stays at d2 after claude-scope apply"
);
let servers = state.db.get_all_mcp_servers().expect("get mcp servers");
@@ -744,3 +741,68 @@ fn profile_switch_auto_disables_takeover_before_apply() {
"live config should point to real endpoint after auto-disable"
);
}
#[cfg(any(target_os = "macos", windows))]
#[test]
fn claude_desktop_profile_scope_is_independent() {
let _guard = test_mutex().lock().expect("acquire test mutex");
reset_test_fs();
let _home = ensure_test_home();
let state = create_test_state().expect("create test state");
state
.db
.save_provider(
AppType::ClaudeDesktop.as_str(),
&desktop_provider("d1", "dk-1"),
)
.expect("save desktop provider d1");
state
.db
.save_provider(
AppType::ClaudeDesktop.as_str(),
&desktop_provider("d2", "dk-2"),
)
.expect("save desktop provider d2");
state
.db
.set_current_provider(AppType::ClaudeDesktop.as_str(), "d1")
.expect("set current desktop provider d1");
// 在 Desktop 页新建项目:只拍 Desktop 供应商
let project = ProfileService::create(&state, "Desktop Project", ProfileScope::ClaudeDesktop)
.expect("create desktop profile");
let payload: ProfilePayload =
serde_json::from_str(&project.payload).expect("parse desktop payload");
assert_eq!(payload.providers.claude_desktop.as_deref(), Some("d1"));
assert_eq!(payload.providers.claude, None, "claude slot untouched");
assert_eq!(payload.providers.codex, None, "codex slot untouched");
// 切到 d2
ProviderService::switch(&state, AppType::ClaudeDesktop, "d2").expect("switch desktop to d2");
// 应用 Desktop 项目:恢复 d1
let (warnings, _) = ProfileService::apply(&state, &project.id, ProfileScope::ClaudeDesktop)
.expect("apply desktop profile");
assert!(warnings.is_empty(), "unexpected warnings: {warnings:?}");
assert_eq!(
state
.db
.get_current_provider(AppType::ClaudeDesktop.as_str())
.expect("get current desktop provider")
.as_deref(),
Some("d1"),
"desktop provider restored by desktop-scope apply"
);
assert_eq!(
state
.db
.get_current_profile_id(ProfileScope::ClaudeDesktop.as_str())
.expect("get desktop current profile id")
.as_deref(),
Some(project.id.as_str()),
"desktop scope marker set"
);
}