From 51ea7fd69f16b250039d2b53ee4396bf6b597ec6 Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Sat, 15 Aug 2026 15:51:56 +0800 Subject: [PATCH] fix(agents): sync Kimi and Grok context windows with CPA core --- src-tauri/src/agents/commands.rs | 9 ++- src-tauri/src/agents/configuration.rs | 6 ++ src-tauri/src/agents/discovery.rs | 26 ++++++- src-tauri/src/agents/state.rs | 3 + src-tauri/src/main.rs | 6 ++ src-tauri/src/tests/agent_configuration.rs | 75 ++++++++++++++++++++- src-tauri/src/tests/agent_state.rs | 44 +++++++++++- src/assets/icons/zcode.png | Bin 0 -> 13843 bytes src/pages/AgentsPage.tsx | 64 +++++++++--------- src/services/agentConfigurationDraft.ts | 5 +- tests/agentModelPicker.test.ts | 17 +++++ 11 files changed, 218 insertions(+), 37 deletions(-) create mode 100644 src/assets/icons/zcode.png diff --git a/src-tauri/src/agents/commands.rs b/src-tauri/src/agents/commands.rs index 27a5efd..29e0054 100644 --- a/src-tauri/src/agents/commands.rs +++ b/src-tauri/src/agents/commands.rs @@ -688,7 +688,7 @@ pub(crate) async fn fetch_prepared_agent_models( prepare_codex_agent_models(&runtime_models) } else { let mut models = fetch_agent_models(config.port, api_key).await?; - if client == AgentClient::ZCode { + if agent_uses_cpa_runtime_context_windows(client) { let runtime_models = fetch_codex_runtime_models(config.port, api_key).await?; codex_catalog::merge_runtime_context_windows(&mut models, &runtime_models); } @@ -714,6 +714,13 @@ pub(crate) async fn fetch_prepared_agent_models( } } +pub(crate) fn agent_uses_cpa_runtime_context_windows(client: AgentClient) -> bool { + matches!( + client, + AgentClient::ZCode | AgentClient::KimiCode | AgentClient::GrokBuild + ) +} + pub(crate) fn resolve_claude_desktop_model_mappings( client: AgentClient, models: &[AgentModelOption], diff --git a/src-tauri/src/agents/configuration.rs b/src-tauri/src/agents/configuration.rs index c2283a4..c38d99d 100644 --- a/src-tauri/src/agents/configuration.rs +++ b/src-tauri/src/agents/configuration.rs @@ -525,6 +525,10 @@ pub(crate) fn build_claude_agent_config( CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV.to_string(), serde_json::Value::String(max_context_tokens.to_string()), ); + env.insert( + CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV.to_string(), + serde_json::Value::String("1".to_string()), + ); env.insert( CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV.to_string(), serde_json::Value::String(mappings.auto_compact_pct.to_string()), @@ -922,6 +926,7 @@ pub(crate) fn remove_claude_code_managed_configuration( "ANTHROPIC_DEFAULT_SONNET_MODEL", "ANTHROPIC_DEFAULT_OPUS_MODEL", "ANTHROPIC_DEFAULT_FABLE_MODEL", + CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV, CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV, CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV, DISABLE_AUTO_COMPACT_ENV, @@ -1582,6 +1587,7 @@ pub(crate) fn build_restored_claude_code_config( "ANTHROPIC_DEFAULT_SONNET_MODEL", "ANTHROPIC_DEFAULT_OPUS_MODEL", "ANTHROPIC_DEFAULT_FABLE_MODEL", + CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV, CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV, CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV, DISABLE_AUTO_COMPACT_ENV, diff --git a/src-tauri/src/agents/discovery.rs b/src-tauri/src/agents/discovery.rs index c06b0b3..131d80f 100644 --- a/src-tauri/src/agents/discovery.rs +++ b/src-tauri/src/agents/discovery.rs @@ -368,6 +368,7 @@ pub(crate) fn inspect_pi_provider_status( config_exists, config_valid, configured, + configuration_synchronized: configured, current_model: current_model.clone(), oauth_configuration: false, modification_enabled: configured, @@ -849,6 +850,7 @@ pub(crate) fn inspect_agent_config( warnings.push(message.clone()); } let modification = inspect_agent_application(client, home); + let configuration_synchronized = agent_configuration_is_synchronized(client, home, configured); warnings.extend(modification.warnings.iter().cloned()); AgentConfigStatus { @@ -865,6 +867,7 @@ pub(crate) fn inspect_agent_config( config_exists, config_valid, configured, + configuration_synchronized, current_model, oauth_configuration, modification_enabled: modification.enabled, @@ -880,6 +883,23 @@ pub(crate) fn inspect_agent_config( } } +pub(crate) fn agent_configuration_is_synchronized( + client: AgentClient, + home: &Path, + configured: bool, +) -> bool { + if !configured { + return false; + } + if !matches!(client, AgentClient::KimiCode | AgentClient::GrokBuild) { + return true; + } + load_agent_applied_state(client, home) + .ok() + .flatten() + .is_some_and(|state| state.configuration_revision >= AGENT_CONFIGURATION_REVISION) +} + pub(crate) fn inspect_agent_application( client: AgentClient, home: &Path, @@ -2063,7 +2083,11 @@ pub(crate) fn inspect_claude_agent_config( && env .and_then(|env| env.get("ANTHROPIC_AUTH_TOKEN")) .and_then(serde_json::Value::as_str) - == Some(api_key); + == Some(api_key) + && env + .and_then(|env| env.get(CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV)) + .and_then(serde_json::Value::as_str) + == Some("1"); let model = env .and_then(|env| env.get("ANTHROPIC_MODEL")) .and_then(serde_json::Value::as_str) diff --git a/src-tauri/src/agents/state.rs b/src-tauri/src/agents/state.rs index 4600f69..d1eed97 100644 --- a/src-tauri/src/agents/state.rs +++ b/src-tauri/src/agents/state.rs @@ -161,6 +161,7 @@ pub(crate) fn recover_codex_applied_state_from_backups( version: AGENT_APPLIED_STATE_VERSION, client: AgentClient::Codex.id().to_string(), model, + configuration_revision: AGENT_CONFIGURATION_REVISION, claude_desktop_model_mappings: None, backup_files, updated_at_unix: SystemTime::now() @@ -284,6 +285,7 @@ pub(crate) fn load_agent_applied_state( version: AGENT_APPLIED_STATE_VERSION, client: client.id().to_string(), model: record.model, + configuration_revision: 0, claude_desktop_model_mappings: None, backup_files, updated_at_unix: SystemTime::now() @@ -1485,6 +1487,7 @@ pub(crate) fn commit_agent_configuration( version: AGENT_APPLIED_STATE_VERSION, client: client.id().to_string(), model: model.to_string(), + configuration_revision: AGENT_CONFIGURATION_REVISION, claude_desktop_model_mappings: (client == AgentClient::ClaudeDesktop) .then(|| claude_desktop_model_mappings.cloned()) .flatten(), diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index bfc43af..5336ec3 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -159,6 +159,8 @@ const MANAGED_CLAUDE_HAIKU_ALIAS_DISPLAY_NAME: &str = const DEFAULT_CLAUDE_CONTEXT_WINDOW: u64 = 200_000; 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_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV: &str = + "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"; 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 = 90; @@ -171,6 +173,7 @@ const MODEL_ALIAS_CONFIG_SECTIONS: &[&str] = &[ const LEGACY_AGENT_MODIFICATION_STATE_VERSION: u8 = 1; const AGENT_MODIFICATION_STATE_VERSION: u8 = 2; const AGENT_APPLIED_STATE_VERSION: u8 = 4; +const AGENT_CONFIGURATION_REVISION: u8 = 1; const AGENT_PHASE_APPLYING: &str = "applying"; const AGENT_PHASE_ACTIVE: &str = "active"; const AGENT_PHASE_RESTORING: &str = "restoring"; @@ -768,6 +771,7 @@ struct AgentConfigStatus { config_exists: bool, config_valid: bool, configured: bool, + configuration_synchronized: bool, current_model: Option, oauth_configuration: bool, modification_enabled: bool, @@ -951,6 +955,8 @@ struct AgentAppliedState { version: u8, client: String, model: String, + #[serde(default)] + configuration_revision: u8, #[serde(default, skip_serializing_if = "Option::is_none")] claude_desktop_model_mappings: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] diff --git a/src-tauri/src/tests/agent_configuration.rs b/src-tauri/src/tests/agent_configuration.rs index 038e563..85cfe58 100644 --- a/src-tauri/src/tests/agent_configuration.rs +++ b/src-tauri/src/tests/agent_configuration.rs @@ -22,6 +22,10 @@ fn claude_agent_config_preserves_existing_fields() { assert_eq!(value["env"]["ANTHROPIC_BASE_URL"], "http://127.0.0.1:8317"); assert_eq!(value["env"]["ANTHROPIC_AUTH_TOKEN"], DEFAULT_API_KEY); assert_eq!(value["env"]["ANTHROPIC_MODEL"], "claude-test"); + assert_eq!( + value["env"][CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV], + "1" + ); assert_eq!(value["env"][CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV], "200000"); assert_eq!(value["env"][CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV], "90"); assert!(value["env"].get(DISABLE_AUTO_COMPACT_ENV).is_none()); @@ -44,6 +48,7 @@ fn claude_code_inspection_normalizes_legacy_1m_suffix() { "env": { "ANTHROPIC_BASE_URL": "http://127.0.0.1:8317", "ANTHROPIC_AUTH_TOKEN": "test-key", + "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY": "1", "ANTHROPIC_MODEL": "deepseek-v4-pro[1m]", "ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek-v4-pro[1m]", "ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-v4-pro[1m]", @@ -71,6 +76,30 @@ fn claude_code_inspection_normalizes_legacy_1m_suffix() { fs::remove_dir_all(directory).unwrap(); } +#[test] +fn claude_code_requires_gateway_model_discovery_to_be_enabled() { + let directory = agent_test_home("claude-code-gateway-model-discovery"); + let path = directory.join("settings.json"); + fs::write( + &path, + r#"{ + "env": { + "ANTHROPIC_BASE_URL": "http://127.0.0.1:8317", + "ANTHROPIC_AUTH_TOKEN": "test-key", + "ANTHROPIC_MODEL": "gpt-test", + "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY": "0" + } + }"#, + ) + .unwrap(); + + let (configured, model) = inspect_claude_agent_config(&path, 8317, "test-key").unwrap(); + + assert!(!configured); + assert_eq!(model.as_deref(), Some("gpt-test")); + fs::remove_dir_all(directory).unwrap(); +} + #[test] fn claude_mapping_legacy_json_defaults_1m_preferences_off() { let mappings: ClaudeDesktopModelMappings = @@ -762,7 +791,9 @@ fn kimi_code_config_preserves_existing_tables_and_uses_openai_provider() { let home = agent_test_home("kimi-code-agent-config"); let path = home.join(".kimi-code/config.toml"); fs::create_dir_all(path.parent().unwrap()).unwrap(); - let models = test_agent_models(&["gpt-test", "deepseek-test"]); + let mut models = test_agent_models(&["gpt-test", "deepseek-test"]); + models[0].context_window = Some(1_048_576); + models[1].context_window = Some(131_072); let rendered = build_kimi_code_agent_config( Some( r#"theme = "dark" @@ -806,6 +837,14 @@ model = "model" .as_str(), Some("tool_use") ); + assert_eq!( + value["models"]["cpa-gui/gpt-test"]["max_context_size"].as_integer(), + Some(1_048_576) + ); + assert_eq!( + value["models"]["cpa-gui/deepseek-test"]["max_context_size"].as_integer(), + Some(131_072) + ); assert_eq!( inspect_kimi_code_agent_config(&path, 8317, DEFAULT_API_KEY).unwrap(), (true, Some("gpt-test".to_string())) @@ -818,7 +857,9 @@ fn grok_build_config_preserves_existing_models_and_uses_chat_completions() { let home = agent_test_home("grok-build-agent-config"); let path = home.join(".grok/config.toml"); fs::create_dir_all(path.parent().unwrap()).unwrap(); - let models = test_agent_models(&["gpt-test", "deepseek-test"]); + let mut models = test_agent_models(&["gpt-test", "deepseek-test"]); + models[0].context_window = Some(1_048_576); + models[1].context_window = Some(131_072); let rendered = build_grok_build_agent_config( Some( r#"theme = "dark" @@ -858,6 +899,14 @@ api_key = "other-key" value["model"]["cpa-gui/gpt-test"]["api_backend"].as_str(), Some("chat_completions") ); + assert_eq!( + value["model"]["cpa-gui/gpt-test"]["context_window"].as_integer(), + Some(1_048_576) + ); + assert_eq!( + value["model"]["cpa-gui/deepseek-test"]["context_window"].as_integer(), + Some(131_072) + ); assert_eq!( inspect_grok_build_agent_config(&path, 8317, DEFAULT_API_KEY).unwrap(), (true, Some("gpt-test".to_string())) @@ -1077,6 +1126,28 @@ fn agent_model_list_parser_exposes_aliases_as_selectable_model_ids() { ); } +#[test] +fn kimi_grok_and_zcode_use_cpa_runtime_context_windows() { + for client in [ + AgentClient::KimiCode, + AgentClient::GrokBuild, + AgentClient::ZCode, + ] { + assert!(agent_uses_cpa_runtime_context_windows(client)); + } + for client in [ + AgentClient::ClaudeCode, + AgentClient::ClaudeDesktop, + AgentClient::Codex, + AgentClient::OpenCode, + AgentClient::OpenClaw, + AgentClient::Hermes, + AgentClient::DeepSeekHarness, + ] { + assert!(!agent_uses_cpa_runtime_context_windows(client)); + } +} + #[test] fn claude_model_lists_mark_yaml_aliases_when_api_returns_only_model_ids() { let mut models = test_agent_models(&[ diff --git a/src-tauri/src/tests/agent_state.rs b/src-tauri/src/tests/agent_state.rs index 98bbfe1..aec01db 100644 --- a/src-tauri/src/tests/agent_state.rs +++ b/src-tauri/src/tests/agent_state.rs @@ -453,6 +453,7 @@ fn zcode_legacy_single_file_state_upgrades_and_restores_cli_config() { version: AGENT_APPLIED_STATE_VERSION, client: AgentClient::ZCode.id().to_string(), model: "gpt-one".to_string(), + configuration_revision: 0, claude_desktop_model_mappings: None, backup_files: vec![AgentAppliedBackupFile { path: paths[0].clone(), @@ -489,9 +490,37 @@ fn zcode_legacy_single_file_state_upgrades_and_restores_cli_config() { fs::remove_dir_all(home).unwrap(); } +#[test] +fn kimi_and_grok_legacy_states_require_context_catalog_resynchronization() { + for client in [AgentClient::KimiCode, AgentClient::GrokBuild] { + let home = agent_test_home(&format!("{}-context-revision", client.id())); + let paths = agent_config_paths(client, &home); + let state_path = agent_state_path(&paths).unwrap(); + fs::create_dir_all(state_path.parent().unwrap()).unwrap(); + let mut state = AgentAppliedState { + version: AGENT_APPLIED_STATE_VERSION, + client: client.id().to_string(), + model: "gpt-test".to_string(), + configuration_revision: 0, + claude_desktop_model_mappings: None, + backup_files: Vec::new(), + updated_at_unix: 1, + }; + write_agent_applied_state(&state_path, &state).unwrap(); + + assert!(!agent_configuration_is_synchronized(client, &home, true)); + + state.configuration_revision = AGENT_CONFIGURATION_REVISION; + write_agent_applied_state(&state_path, &state).unwrap(); + assert!(agent_configuration_is_synchronized(client, &home, true)); + assert!(!agent_configuration_is_synchronized(client, &home, false)); + fs::remove_dir_all(home).unwrap(); + } +} + #[test] fn session_merge_preserves_runtime_fields_for_other_agent_formats() { - let claude_original = r#"{"env":{"KEEP_ENV":"original"},"keep":"claude"}"#; + let claude_original = r#"{"env":{"KEEP_ENV":"original","CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY":"0"},"keep":"claude"}"#; let claude_managed = build_claude_agent_config( Some(claude_original), "http://127.0.0.1:8317", @@ -516,6 +545,10 @@ fn session_merge_preserves_runtime_fields_for_other_agent_formats() { assert_eq!(claude["runtimeAdded"], true); assert_eq!(claude["env"]["RUNTIME_ENV"], "keep"); assert_eq!(claude["env"]["KEEP_ENV"], "original"); + assert_eq!( + claude["env"][CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV], + "0" + ); assert!(claude["env"].get("ANTHROPIC_BASE_URL").is_none()); assert!(claude.get("model").is_none()); @@ -735,6 +768,7 @@ fn legacy_codex_single_file_state_remains_restorable() { version: AGENT_APPLIED_STATE_VERSION, client: AgentClient::Codex.id().to_string(), model: "gpt-test".to_string(), + configuration_revision: AGENT_CONFIGURATION_REVISION, claude_desktop_model_mappings: None, backup_files: vec![AgentAppliedBackupFile { path: path.clone(), @@ -799,6 +833,7 @@ fn claude_desktop_version_three_state_can_be_closed_safely() { version: 3, client: AgentClient::ClaudeDesktop.id().to_string(), model: "gpt-test".to_string(), + configuration_revision: 0, claude_desktop_model_mappings: None, backup_files: Vec::new(), updated_at_unix: 1, @@ -930,6 +965,7 @@ fn applied_state_rejects_backup_paths_outside_the_managed_config_set() { version: AGENT_APPLIED_STATE_VERSION, client: AgentClient::OpenCode.id().to_string(), model: "gpt-test".to_string(), + configuration_revision: AGENT_CONFIGURATION_REVISION, claude_desktop_model_mappings: None, backup_files: vec![AgentAppliedBackupFile { path: path.clone(), @@ -966,6 +1002,7 @@ fn missing_session_backup_is_detected_before_any_file_is_restored() { version: AGENT_APPLIED_STATE_VERSION, client: AgentClient::Codex.id().to_string(), model: "gpt-test".to_string(), + configuration_revision: AGENT_CONFIGURATION_REVISION, claude_desktop_model_mappings: None, backup_files: vec![ AgentAppliedBackupFile { @@ -1003,6 +1040,7 @@ fn version_three_cleanup_covers_every_non_desktop_agent_format() { version: 3, client: client.id().to_string(), model: "gpt-test".to_string(), + configuration_revision: 0, claude_desktop_model_mappings: None, backup_files: Vec::new(), updated_at_unix: 1, @@ -1017,6 +1055,7 @@ fn version_three_cleanup_covers_every_non_desktop_agent_format() { "ANTHROPIC_BASE_URL": "http://127.0.0.1:8317", "ANTHROPIC_API_KEY": "secret", "ANTHROPIC_AUTH_TOKEN": "secret", + "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY": "1", "ANTHROPIC_MODEL": "gpt-test", "ANTHROPIC_DEFAULT_HAIKU_MODEL": "gpt-test", "KEEP_ENV": "yes" @@ -1039,6 +1078,9 @@ fn version_three_cleanup_covers_every_non_desktop_agent_format() { serde_json::from_str(&fs::read_to_string(&claude_path).unwrap()).unwrap(); assert!(claude.get("model").is_none()); assert!(claude["env"].get("ANTHROPIC_BASE_URL").is_none()); + assert!(claude["env"] + .get(CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV) + .is_none()); assert_eq!(claude["env"]["KEEP_ENV"], "yes"); assert_eq!(claude["keep"], "claude"); diff --git a/src/assets/icons/zcode.png b/src/assets/icons/zcode.png new file mode 100644 index 0000000000000000000000000000000000000000..366b1faaa83f4fe71da06acbeb1e4fbb67f4718b GIT binary patch literal 13843 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 z z05bL>Kuc8+iKxEYcBiHKHeI)3SPni_p%G~4KS&(ZOx1j?+!oPS;*W1(y+!lAlxP%{ z)d9|N(p`8)z#F%k#)cvA(Ea3Q`BmHdEU+1erg#b0nx_Ivy4M5Fa885ZVj-BlQ$vB{`B0~*3h8;cMZi`ScvfDh53a|> zUJja|>DYlT5Sp<2%uQbk02DhaZD!HLZ~NGz(NUN}Tl$5M?aQUGdzvvXx72-0ZIb%y zo0>dw=Y7Ur-{x-(`SY6LVp%NtUlX)iE3q7D0Uqg#!-gi9vg4KpB!A#Mbutop2nMD0NP0joYmh=X{uT*oMX$yW@Jn>HrfsMM^ti5|hM` z1W6g)vX2g%o&K`zT8Q1=V{%@(vvadxet!Y;T%bO(T*0dTYigZe^cf!$swaB9hBmH#4 z2g)SxP--6rO7{+x(_$CLF$$0(=43l1SN0^(uXT>H$Sbr`{=5H5j*3>K)mW zpYn|~b;FY*Utm7iE4I(YSV#B>n2LVP5qQybZx*V|L4!x2S($IS0Fk8PI)gH}V0`L) za~V!OMM=wa?IaBR=YxkSHbb-1ggS;UbZq#DT~?;Lty8f5_MppRR{!^FA}xDt99`fA zT7WsIfEF;-+l%Uti18&&WhQ1^C#1e=9GFp*$8%k?McpESkK-RAKR@Sc#q5oYD}4X` z{<`X)r(I{4^hb3n2yp$-NH=E=@);u9iyUQ0b@sj%0=ZQU49I<<_+vuA^p4S}>9r>k z;+Fg%O8(+%h#GpX44$Iqb1xS+=z7mc^b)X2!@f6v{mzw9hsfk6FZ?*hJya}_<9|^9 zhnAHF@=2L?RLuxP2*e54VgXy+z|Iyx{RJ57TzQc*K(fjHdx?6p&U>s6I7R{^OPBLQ%-t;GUu! zbc{_(*B6pjG%E`C>Mt$6g|EMX>@IhZ}vAU3#{whv*Bj5yf|oC3hjKW{a^zhw0jM>!*D1+&XdVT zW{s{6gC{hpuikhKV`w_gW!)hruKT)z+H`Ca%dEDV`l6c_yO;a4- z>$J>fyq_x&!JJ#NasxM8NMDj!T{yY!PdOrWn}Pinf~O)u4F>_P#H3kA5-rRW;H(ZG1fvv03igf9FTIg+}F$9Qy4W?-}`tIIwHELNiro|@8AZVD{%bKa+Iwk>vkzjYPsfz zz?$b7XM=hzAc2ei2;X(JCGjz`N__Rg}jzn}8le#T{{NfJ&;m z-3~t&z!P~e>l}xNs(h8LqM@@@X~$Dm&3H!WDR7g5k((>a19a;>i^jQrbn1m~`VAwu z@D(kok+7tY#Wo;$T$|t>+i`m%#!L2(&eP`oCD(?9J9uIC&>%toEu_W6g=3p4bHtxd zMW8hmNol52+A&1?6lv4GkE?Y z&D8Cd;?4G3yc+R^Gm}T*$XL1fV0O8bP5-~Af1TR0?wc+SO6Pok37B0ZBBkvhFQ1rA zOo@#Dh0+<|P-~cLwA6}LUTo%<0yW_}PX=#chwh{NkE*c(5F9OsS__Sj6D~IQSmS~L zAV(5L7WOca+n-)>?ZvuD87os-@~g?>hHjBu^Z6Kc%kfwY*X5C~!t2yHyw98a&AFa- zXs6*ss8P>8Tbty>x#|tp<}Vu7%D1(Z?XjKcfNdWO49%pioxKJeQ?Ppp^Bv}QJZX4g z46x&#esxtBq9x?`xHJy^JU~A{F!%v%v4jNA2hiau?5#_bHhtwajFb!@a!s$Ovz-&9YVgu0fE}j)siwkqu#`QdFBSR#?RHy}grLzxb751t+ zzvr4v_z^3L^O1GH_N>N=vLjezJ}gU)rX|WA_g*%`r(GNJ^1MO08evZL%^HOU=z#tj zQ0EEniel4I57R9Or?b2n8RlxPPy(lxh_)L4cEZ!VfCP5}gYt=NzWUjrH;GY$JTR_Q z-HG)TYvXPtpeeAQr}#Et+~An=j+540J>KHr*WvG?TtC{4uWaji1=#BO2KXBp9O*6J zbc=3#`0_S65a(_%b_fIYR`I~P)5=Owg~3-RG1)M+Ch->{{7d14nU^;-MefY7pB4%#Imp4DIR#)ol3v92!_n|&R-Vp(KoKQRct-77**JwlL+_P}^s``(A z;ZdQBcR-NbtmX9TrE!Lt#R;Jfp4BqsO%(?A0+}#0LaXJBZ^Zw;{P0A3X$% zUyN1pF4qpB5FYf)9>4s5K);cR2|hK(y3+?)A6i&sb#keLhfm1g zZ9}kbKAZl3uXMr)g`YiZSko_8cqLQJ^+y0cWDz8QPZL|8`6@p_&&vyL{xh=I6W0Qq ziVkxF??2WUQw*3pLZI-b-6UW;3zWNYVYqgx2Hxca$hi|d#be{F_qm<8-{yV-gCz1f zArg|Cxsj_Rq2pxV-xEK1`y>~_M1MU9h)xGAiqa@Xt-OwSb?V(aDxfe|Lw*(xp$AE8XMdo+O}e_)1`tC?7Wk2Itix^2H-7p3Tc?95qzK4zwYyQRrIRWbq=;n zZh86q@aAMFpUicu9lcM@+;Bf7>Og81ntE@}jN!@%61u29-H8)|-%*^D&r}D*fSVKY)cGElf-;xb}O*IHswTb6=bvjMi zf|h5&U*!wF4%BuZef>lmmLnFn@`#-i(6@5S6lM-BcU)i0uN2+dvn7T|KDD~f1OVmK5Pi`l`NCFWm4*w z5%Gut$PE-{Ot5Hr)5NttE$L6;-jvbtkmY@Nn6BE>5tShD>dFT>Jwel%7zrY_C-MOqvy6 z;2*Q`;N!}zk>k2U?R5RoX&VhTmTUq~|KrH@$zR7ZC{~7w3TRH_)X1#-n2nn6zBV<< z*$9DB3le|q7nPK%891uV{Th2Ies{C4;m6&DHhK!;qZz1$jTTzl*FMXWxa?uhSFx~G z%x#rtu2v{NjhcViz8e6q(Bo}!3?TCuc|j+IKO}xVbb^ib(;)-|d@9gv{3?J+R6Cd7 zZ1*ppxWnqd4BX|L9c5a)>=_w{9znOMB$vaM@ePFlUbqSze+NORBsV4o zyP-KHTxFpvXwygfBxhrRM-a&EaSSjZdee=-SNYUb_F*3hY zVd3Aqo8m2@g%<3fUwdSkm{JO06UM~}7$G@1eN}LAa3<&I=U{0Vl(vrog+ zo6NAaWy+P({S?%S1Vu0DH{gxC-|Vb3C3w|yE+Vh4t`gJ4!Y*^$-Zb4bY#IsbY}iR3 zj#qz&5gF7{qj3GF2<$t?%$a=BE(+Am0?dEiz$C0C?apr+s)_;}S+uBk z^nDu>V2y!;54j={?8qNf#Y0;h+JfHF4&*x?|KBtLL@DhBixzZDImhgu&+x-Z{EW-$ zwv=fW1#2XKoOG6avEjL!sY&g>I!(Z8E^_^Ny&|6{g@KijIDpI?SYZnM9isKl@GDZ* zg39Lni{sFx!i?v?KY`b5d+4Xl@spy!Pdg8%d=;{&o)D*yiyqQQ*cz|(+39BoQ}CK{ zGYK&kuIa%fjZ?GCikBI13^L5r7l?^IL~d8`vR2*XDd6bvd8wXsGWEys z@NmYJjH~-{y5+tg?sOzqLAIE%Fn!FM(o+sbHIqf6+`lhK_M7*2eg{?JmBIW=;6$(Y zD<|u*6f23A>g4+NN!ZET`fPhPAHPK{ONp{v*klMXmN z?)5m%vd9U!!!UJ%4b%ED`xMX&D#3?fntCBLFZo4hV-LS{JkD`38LOSlv)Xk^0FFM@!GKTTg&;G9Sim6=aj|Ex+!qyuX>)9iB9G*PkJLbaX)0Y zOZ$6K=aK)r$6F7X64$E)O$?lOCVxI)Hk7{i+Oog!&DKpy7g@^qx(@Y@l<^hadQCij zy5I|km`f|gH_hCfBXFbKq}x3VbpXyc&Q2edTmoAgz2D=z@T{DP^vX-Fzf|~@Tz`MU zGpai>cBvGgvjm)@eE(n>u25{#obl%rz~Rj6r~p4B zAYe(97E2RmXWaIt)Q1V>09px}6{A3bzdzHsi*2by+g#tzurv0SUIJ0@So|r$e{;{r z|EI_+l1vo?I?dIwC$-I1G|;DCvL(zKPNk5w4;x%&o{It6Vk@l!+bp_#7jVmI>O=Xz zl#8vIklaEq6EG(*cgz`S3PQBM zJc+Zsmghj*#iq^4v|b^lEAS&fO0rO)hQB-y4YGQ;)Wy>PuLv!?;lM< zzixK2n~>vMg{VYS7Lr(W{=+CxMQZIagrI@?n&r6AHH9gSA+rqiKmyUnguAj)_CV7G zuX!$?*YsfDdeyq3h4@nY-A?J2qx_G!ghu$oSbg#6Koza@+Wdi$L z5|cf50$-IJjn)@pUVF=SRwt1M73cp8UrrKbJ;3||i7He0~rk0;OO zaLA`7ySa{#jewSP^+IK4#Fe#Z5-Ej`Zqig$t5-8em1V$sT}Rick?a{Jux)Ybx(mw3 zo>?U4v(t6b>RP$e-shR6mSM#}f1k`}0mq80h3Vs))Ge}^-$FN~pNiz#52hUNJqdk& zD3^$m9UvU&$mMQK%8AT##{F(RZlR$WA|7__JOpwer0@>C@vVb_-hpxshSO*=TZ|dR z`dl%A4{aodu%f!`t7llL!sHk%bf7ndlo87?NIdFfHy zitAnh3J6d6uyg_m+G*JgJ?%8Qxd4}3iZsK|>$919LzmFSLHvkU3G;85g1%rn^`{{$ zfeHHi%ueWokw5@Zkwq06FXBLwEXze^5dKctBfJN1qyLDz2m*|W_a0mJ&cc3H+MLID z`7>u_xe#1L&2c}gbHq;vD~38@Wd_Ayt~f{8%PmLv>$=Tn-VHa$G|7Zi9?TmMm$`Pn z5uI`CwdlG%A1;suH=n=q6C|0b1GUI_!&Pl{>S*j`VloHUNzA$3)yNSeuFytnE3w~s zAHKJX_n!j)!vlL_uolg$u*JAUArgz-=6zk5>rw`(*AL*AVqPJ-Ve4p?7vp(1lJbUJ zmu4*XW0zI`O8b%Bf3V;M-vxyNl90{6+6~PE%h0gR>rW3~F(#;{=iJM!Di}r3xXfY2 z=3s2rApA)3J;k?JK79DDjN7A#6rdJI|3!km?$8l$5s;N8z9Y^F3e28{?ZvsyVMWG- ziHXZ&!8NXEuPI+kuIv1bfVr(esD%&Tb&T9()+XcUNh0oFqhjMe+W%ocJ>rZoluGpe z;yULy4a3M3)lg6rMFrva_yjY-a)z%=r?p;9tqN^3PR0hA*pVr-gQ`a(E@2P#H?Kcq zl~N`m!O+6^!?Iu`OHLi~2~%W^HJ5fsDba7J2sB zKnj@hWd-H1UL0$AfnOQw45&^>GDh;y(dnP$AR2Fb2@5{|?~X}R+NndKj@ZM4@vkTx-Oxk#23ItEEF#Cw__T92AB9zp%(ekG&h=p7PO$movJS6XMXWtBB~XiGv98mKMu5* zgY6B?!a^cl=Nj-@6u__8eeS#s=Bj{?z9HASMeDh9c+b$In@ra^$Q%rl;W~G_s#1QV zwA9rB8iP)-`r>`?2r)cy?w>V2sAph%F{|Cy-!Ti0bLWPRjCf%ijz@_(JbAd{T-wN; z<|>HcQ5UW1yy2hRC5YSBh<(dud7JnIL}A&Q7)$&lbCT4dCh0xVX*9>repM(dIUDz^ zP-w|Z+cV|$Z_g=KS+v*$yv#3B@mcA+AeX}k0uN|LX)P*uSW(UUp7@P**H$zPqxw%h z5fc%1#vJ$|#N&5a`;GWxW`hX)k=i-OsziYIyejx^!_0^{+S~l%f@cfi%9JyKq79!X zDbc=R{mC<8T6(%mmi^LUcx)zTe%RNM>+&SISnM{ zqDE;iVpm=qxrDoI4L^dsq!CrIzc)vAtDm3ue?Oh=5k1yF#W=D)wv>Ax6b@? zqs))|I+s8!Q_u5rc8tBay9KiqLMtsY>{4U$bN@LPxrtfP!Q0_)Ia*J|HwVPw^lP6! zD$ZmvW_$%L7!>?bobpX!=H3~5He0GZ{5QPv>fP=`aGk9mS&E{q3vDh$o?WPi)pMcx zZ!h~t%_4rA^yFfUH$s}K0vaFhlAN5KhC4p`xMyuVxsQJZ#dMFcVUD~+fM;t=T;8H_ zXH2me`C^-3oNjZF=8RHo+5Y2dshOk(yH3}bG{_g!e>2Xp3U~bROzK}r@~|CIc+p3c zm9fF+hS}VZe>3i^-`0Dzl-)T$Vcd00bu-CyWZq=btnOTR-Q*@}L@sFVP}-X-Lu z)@Rsx+Nth?nE3=(OkTCyDhWGg8o)Jf{q2zWzwjQ@uxb&G8kIia&8f2Lh<}j_@alz@ KO67B_i2nuo7^Ct4 literal 0 HcmV?d00001 diff --git a/src/pages/AgentsPage.tsx b/src/pages/AgentsPage.tsx index 659d047..9a10840 100644 --- a/src/pages/AgentsPage.tsx +++ b/src/pages/AgentsPage.tsx @@ -32,6 +32,7 @@ import kimiIcon from '../assets/icons/kimi-light.svg'; import openclawIcon from '../assets/icons/openclaw.svg'; import opencodeIcon from '../assets/icons/opencode.svg'; import piIcon from '../assets/icons/pi-logo-on-light.svg'; +import zcodeIcon from '../assets/icons/zcode.png'; import { agentModelAlias, filterAgentModels, @@ -79,6 +80,7 @@ type AgentConfigStatus = { pluginVersion: string | null; configValid: boolean; configured: boolean; + configurationSynchronized: boolean; currentModel: string | null; oauthConfiguration: boolean; modificationEnabled: boolean; @@ -205,12 +207,42 @@ const agentDefinitions: AgentDefinition[] = [ icon: codexIcon, descriptionKey: 'agents.description.codex', }, + { + id: 'deepseek-harness', + name: 'DeepSeek Harness', + icon: deepseekIcon, + descriptionKey: 'agents.description.deepseekHarness', + }, { id: 'opencode', name: 'OpenCode', icon: opencodeIcon, descriptionKey: 'agents.description.opencode', }, + { + id: 'pi', + name: 'Pi', + icon: piIcon, + descriptionKey: 'agents.description.pi', + }, + { + id: 'grok-build', + name: 'Grok Build', + icon: grokIcon, + descriptionKey: 'agents.description.grokBuild', + }, + { + id: 'zcode', + name: 'ZCode', + icon: zcodeIcon, + descriptionKey: 'agents.description.zcode', + }, + { + id: 'kimi-code', + name: 'Kimi Code', + icon: kimiIcon, + descriptionKey: 'agents.description.kimiCode', + }, { id: 'openclaw', name: 'OpenClaw', @@ -223,36 +255,6 @@ const agentDefinitions: AgentDefinition[] = [ icon: hermesIcon, descriptionKey: 'agents.description.hermes', }, - { - id: 'deepseek-harness', - name: 'DeepSeek Harness', - icon: deepseekIcon, - descriptionKey: 'agents.description.deepseekHarness', - }, - { - id: 'zcode', - name: 'ZCode', - Icon: Bot, - descriptionKey: 'agents.description.zcode', - }, - { - id: 'kimi-code', - name: 'Kimi Code', - icon: kimiIcon, - descriptionKey: 'agents.description.kimiCode', - }, - { - id: 'grok-build', - name: 'Grok Build', - icon: grokIcon, - descriptionKey: 'agents.description.grokBuild', - }, - { - id: 'pi', - name: 'Pi', - icon: piIcon, - descriptionKey: 'agents.description.pi', - }, ]; const agentSubpages: AgentSubpageDefinition[] = [ @@ -885,7 +887,7 @@ export function AgentsPage() { const configurationAction = resolveAgentConfigurationAction({ client: selected, modificationState: activeStatus?.modificationState ?? 'unconfigured', - configurationSynchronized: Boolean(activeStatus?.configured), + configurationSynchronized: Boolean(activeStatus?.configurationSynchronized), selectedModel, appliedModel, oauthConfiguration, diff --git a/src/services/agentConfigurationDraft.ts b/src/services/agentConfigurationDraft.ts index a8bf8cb..000330e 100644 --- a/src/services/agentConfigurationDraft.ts +++ b/src/services/agentConfigurationDraft.ts @@ -93,7 +93,10 @@ export function resolveAgentConfigurationAction({ }: ResolveAgentConfigurationActionOptions): AgentConfigurationAction { if (modificationState !== 'applied') return 'apply'; if (client === 'pi') return 'close'; - if (client === 'zcode' && !configurationSynchronized) return 'update'; + if ( + (client === 'zcode' || client === 'kimi-code' || client === 'grok-build') + && !configurationSynchronized + ) return 'update'; const modelChanged = client === 'claude-code' || client === 'claude-desktop' ? !sameAgentModelMappings(modelMappings, appliedModelMappings) diff --git a/tests/agentModelPicker.test.ts b/tests/agentModelPicker.test.ts index ab709df..269f9ad 100644 --- a/tests/agentModelPicker.test.ts +++ b/tests/agentModelPicker.test.ts @@ -175,6 +175,23 @@ describe('agent configuration update action', () => { })).toBe('update'); }); + test.each(['kimi-code', 'grok-build'])( + '%s updates an applied configuration whose CPA context catalog is stale', + (client) => { + expect(resolveAgentConfigurationAction({ + client, + modificationState: 'applied', + configurationSynchronized: false, + selectedModel: 'model-a', + appliedModel: 'model-a', + oauthConfiguration: false, + appliedOauthConfiguration: false, + modelMappings: { opus: '', sonnet: '', haiku: '' }, + appliedModelMappings: { opus: '', sonnet: '', haiku: '' }, + })).toBe('update'); + }, + ); + test('Codex also updates when only OAuth configuration changes', () => { expect(resolveAgentConfigurationAction({ client: 'codex',