From 4484e3a1901c8df0e008e6b1d6f09089dd77140d Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Tue, 11 Aug 2026 14:04:46 +0800 Subject: [PATCH] fix(agents): isolate Claude client model mapping drafts --- src-tauri/src/main.rs | 2 +- src-tauri/src/tests/agent_configuration.rs | 6 ++--- src/i18n/ja.ts | 2 +- src/i18n/locales/en.ts | 2 +- src/i18n/locales/zh-CN.ts | 2 +- src/pages/AgentsPage.tsx | 29 ++++++++++++++-------- src/services/agentConfigurationDraft.ts | 9 ++++++- tests/agentModelPicker.test.ts | 29 ++++++++++++++++++++++ 8 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6567cf5..ff8b9a5 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -146,7 +146,7 @@ const CLAUDE_DESKTOP_EXTENDED_CONTEXT_WINDOW: u64 = 1_000_000; const CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV: &str = "CLAUDE_CODE_MAX_CONTEXT_TOKENS"; const CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV: &str = "CLAUDE_AUTOCOMPACT_PCT_OVERRIDE"; const DISABLE_AUTO_COMPACT_ENV: &str = "DISABLE_AUTO_COMPACT"; -const DEFAULT_CLAUDE_AUTO_COMPACT_PCT: u8 = 100; +const DEFAULT_CLAUDE_AUTO_COMPACT_PCT: u8 = 90; const MODEL_ALIAS_CONFIG_SECTIONS: &[&str] = &[ "codex-api-key", "openai-compatibility", diff --git a/src-tauri/src/tests/agent_configuration.rs b/src-tauri/src/tests/agent_configuration.rs index a5ebd70..16ea01e 100644 --- a/src-tauri/src/tests/agent_configuration.rs +++ b/src-tauri/src/tests/agent_configuration.rs @@ -23,7 +23,7 @@ fn claude_agent_config_preserves_existing_fields() { assert_eq!(value["env"]["ANTHROPIC_AUTH_TOKEN"], DEFAULT_API_KEY); assert_eq!(value["env"]["ANTHROPIC_MODEL"], "claude-test"); assert_eq!(value["env"][CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV], "200000"); - assert_eq!(value["env"][CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV], "100"); + assert_eq!(value["env"][CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV], "90"); assert!(value["env"].get(DISABLE_AUTO_COMPACT_ENV).is_none()); assert!(value["env"].get("CLAUDE_CODE_EFFORT_LEVEL").is_none()); assert_eq!(value["env"]["CLAUDE_CODE_SUBAGENT_MODEL"], "claude-test"); @@ -66,7 +66,7 @@ fn claude_code_inspection_normalizes_legacy_1m_suffix() { assert!(mappings.sonnet_1m); assert!(!mappings.haiku_1m); assert_eq!(mappings.max_context_tokens, 1_000_000); - assert_eq!(mappings.auto_compact_pct, 100); + assert_eq!(mappings.auto_compact_pct, 90); assert!(!mappings.disable_auto_compact); fs::remove_dir_all(directory).unwrap(); } @@ -80,7 +80,7 @@ fn claude_mapping_legacy_json_defaults_1m_preferences_off() { assert!(!mappings.sonnet_1m); assert!(!mappings.haiku_1m); assert_eq!(mappings.max_context_tokens, 200_000); - assert_eq!(mappings.auto_compact_pct, 100); + assert_eq!(mappings.auto_compact_pct, 90); assert!(!mappings.disable_auto_compact); } diff --git a/src/i18n/ja.ts b/src/i18n/ja.ts index 5e4d014..a37d0a8 100644 --- a/src/i18n/ja.ts +++ b/src/i18n/ja.ts @@ -576,7 +576,7 @@ export const jaOverrides = { 'agents.claudeCodeRuntime.maxContextTokensHint': 'Claude Code の既定値は 200000 です。上流モデルの上限を超えないでください。', 'agents.claudeCodeRuntime.context1mHint': 'このマッピングで [1m] モデルを使用し、最大コンテキストを 1000000 にします。3 つすべてを無効にすると 200000 に戻ります。', 'agents.claudeCodeRuntime.autoCompactPct': '自動圧縮の開始率', - 'agents.claudeCodeRuntime.autoCompactPctHint': '既定値は 100% です。小さい値ほど早く圧縮します。', + 'agents.claudeCodeRuntime.autoCompactPctHint': '既定値は 90% です。小さい値ほど早く圧縮します。', 'agents.claudeCodeRuntime.disableAutoCompact': '自動圧縮を無効化', 'agents.claudeCodeRuntime.disableAutoCompactHint': '有効時は /compact による手動圧縮のみ利用できます。', 'agents.claudeDesktopMapping.customMapping': 'モデルエイリアスを使用', diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 33ec00d..67fb223 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -571,7 +571,7 @@ export const en: Record = { 'agents.claudeCodeRuntime.maxContextTokensHint': 'Claude Code defaults to 200000. Do not exceed the upstream model limit.', 'agents.claudeCodeRuntime.context1mHint': 'Uses the [1m] model for this mapping and sets the maximum context to 1000000. It returns to 200000 after all three mappings are off.', 'agents.claudeCodeRuntime.autoCompactPct': 'Auto-compact trigger', - 'agents.claudeCodeRuntime.autoCompactPctHint': 'Defaults to 100%. Lower values compact earlier.', + 'agents.claudeCodeRuntime.autoCompactPctHint': 'Defaults to 90%. Lower values compact earlier.', 'agents.claudeCodeRuntime.disableAutoCompact': 'Disable auto-compaction', 'agents.claudeCodeRuntime.disableAutoCompactHint': 'Only manual /compact remains available when enabled.', 'agents.claudeDesktopMapping.customMapping': 'Use Model Aliases', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 08061f6..7075629 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -569,7 +569,7 @@ export const zhCN = { 'agents.claudeCodeRuntime.maxContextTokensHint': 'Claude Code 默认 200000;请勿超过上游模型实际支持的窗口。', 'agents.claudeCodeRuntime.context1mHint': '为此映射使用 [1m] 模型并将最大窗口设为 1000000;三个映射均关闭后恢复为 200000。', 'agents.claudeCodeRuntime.autoCompactPct': '触发压缩百分比', - 'agents.claudeCodeRuntime.autoCompactPctHint': '默认 100;调低后会更早自动压缩。', + 'agents.claudeCodeRuntime.autoCompactPctHint': '默认 90%;调低后会更早自动压缩。', 'agents.claudeCodeRuntime.disableAutoCompact': '禁止自动压缩', 'agents.claudeCodeRuntime.disableAutoCompactHint': '开启后仅允许用户通过 /compact 手动压缩。', 'agents.claudeDesktopMapping.customMapping': '使用模型别名', diff --git a/src/pages/AgentsPage.tsx b/src/pages/AgentsPage.tsx index 279b228..d008e74 100644 --- a/src/pages/AgentsPage.tsx +++ b/src/pages/AgentsPage.tsx @@ -39,6 +39,7 @@ import { } from '../services/agentModelPicker'; import { resolveAgentConfigurationAction, + resolveAgentModelMappingsDraftSource, sameAgentModel, sameAgentModelMappings, } from '../services/agentConfigurationDraft'; @@ -111,7 +112,7 @@ type ClaudeModelMappings = { const CODEX_OAUTH_LOGIN_REQUIRED_ERROR = 'CODEX_OAUTH_LOGIN_REQUIRED'; const DEFAULT_CLAUDE_CODE_MAX_CONTEXT_TOKENS = 200_000; -const DEFAULT_CLAUDE_AUTO_COMPACT_PCT = 100; +const DEFAULT_CLAUDE_AUTO_COMPACT_PCT = 90; const createClaudeModelMappings = (model: string): ClaudeModelMappings => ({ opus: model, @@ -720,18 +721,24 @@ export function AgentsPage() { const appliedMappings = selected === 'claude-code' ? activeStatus?.claudeCodeModelMappings : activeStatus?.claudeDesktopModelMappings; - if (!claudeModelMappingsDirtyRef.current && appliedMappings) { - const appliedModels = claudeMappingRoles - .map((role) => findAgentModel(models, appliedMappings[role.key])) - .filter((model): model is ModelOption => model !== null); - if (appliedModels.length === claudeMappingRoles.length) { - setClaudeCustomMapping(appliedModels.every((model) => Boolean(model.isAlias))); - } + if (!claudeModelMappingsDirtyRef.current) { + const appliedModels = appliedMappings + ? claudeMappingRoles + .map((role) => findAgentModel(models, appliedMappings[role.key])) + .filter((model): model is ModelOption => model !== null) + : []; + setClaudeCustomMapping( + appliedModels.length === claudeMappingRoles.length + && appliedModels.every((model) => Boolean(model.isAlias)), + ); } setClaudeModelMappingsDraft((current) => { - const source = claudeModelMappingsDirtyRef.current - ? current - : appliedMappings ?? current; + const source = resolveAgentModelMappingsDraftSource( + current, + appliedMappings, + createClaudeModelMappings(selectedModel), + claudeModelMappingsDirtyRef.current, + ); const next: ClaudeModelMappings = { opus: findAgentModel(models, source.opus)?.name ?? selectedModel, sonnet: findAgentModel(models, source.sonnet)?.name ?? selectedModel, diff --git a/src/services/agentConfigurationDraft.ts b/src/services/agentConfigurationDraft.ts index 6699acf..ad7c5a9 100644 --- a/src/services/agentConfigurationDraft.ts +++ b/src/services/agentConfigurationDraft.ts @@ -50,9 +50,16 @@ export const sameAgentModelMappings = ( && Boolean(left.sonnet1m) === Boolean(right.sonnet1m) && Boolean(left.haiku1m) === Boolean(right.haiku1m) && (left.maxContextTokens ?? 200_000) === (right.maxContextTokens ?? 200_000) - && (left.autoCompactPct ?? 100) === (right.autoCompactPct ?? 100) + && (left.autoCompactPct ?? 90) === (right.autoCompactPct ?? 90) && Boolean(left.disableAutoCompact) === Boolean(right.disableAutoCompact); +export const resolveAgentModelMappingsDraftSource = ( + current: T, + applied: T | null | undefined, + fallback: T, + dirty: boolean, +): T => (dirty ? current : applied ?? fallback); + export function resolveAgentConfigurationAction({ client, modificationState, diff --git a/tests/agentModelPicker.test.ts b/tests/agentModelPicker.test.ts index df7d980..0476abd 100644 --- a/tests/agentModelPicker.test.ts +++ b/tests/agentModelPicker.test.ts @@ -10,6 +10,8 @@ import { } from '../src/services/agentModelPicker'; import { resolveAgentConfigurationAction, + resolveAgentModelMappingsDraftSource, + sameAgentModelMappings, type AgentConfigurationClientId, } from '../src/services/agentConfigurationDraft'; @@ -98,6 +100,33 @@ const appliedConfiguration = ( }); describe('agent configuration update action', () => { + test('Claude mapping clients do not inherit another client draft', () => { + const codeDraft = { opus: 'code-opus', sonnet: 'code-sonnet', haiku: 'code-haiku' }; + const desktopFallback = { + opus: 'desktop-model', + sonnet: 'desktop-model', + haiku: 'desktop-model', + }; + expect(resolveAgentModelMappingsDraftSource( + codeDraft, + null, + desktopFallback, + false, + )).toEqual(desktopFallback); + expect(resolveAgentModelMappingsDraftSource( + codeDraft, + null, + desktopFallback, + true, + )).toEqual(codeDraft); + }); + + test('Claude Code missing auto-compact preference defaults to 90%', () => { + const mappings = { opus: 'model-a', sonnet: 'model-a', haiku: 'model-a' }; + expect(sameAgentModelMappings(mappings, { ...mappings, autoCompactPct: 90 })).toBeTrue(); + expect(sameAgentModelMappings(mappings, { ...mappings, autoCompactPct: 100 })).toBeFalse(); + }); + test.each([ 'claude-code', 'claude-desktop',