diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 9ed6cce..afdcc6b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -708,7 +708,6 @@ struct AgentAppliedState { version: u8, client: String, model: String, - managed_fingerprint: String, updated_at_unix: u64, } @@ -2443,7 +2442,7 @@ fn inspect_agent_config( if let Some(message) = error.as_ref() { warnings.push(message.clone()); } - let modification = inspect_agent_application(client, home, configured); + let modification = inspect_agent_application(client, home); warnings.extend(modification.warnings.iter().cloned()); AgentConfigStatus { @@ -2468,55 +2467,21 @@ fn inspect_agent_config( } } -fn inspect_agent_application( - client: AgentClient, - home: &Path, - configured: bool, -) -> AgentModificationInspection { +fn inspect_agent_application(client: AgentClient, home: &Path) -> AgentModificationInspection { match load_agent_applied_state(client, home) { - Ok(Some(state)) => match agent_managed_fingerprint(client, home) { - Ok(fingerprint) if fingerprint == state.managed_fingerprint && configured => { - AgentModificationInspection { - enabled: true, - state: "applied".to_string(), - backup_available: false, - applied_model: Some(state.model), - warnings: Vec::new(), - } - } - Ok(_) => AgentModificationInspection { - enabled: false, - state: "external-changed".to_string(), - backup_available: false, - applied_model: Some(state.model), - warnings: vec![ - "配置已被外部程序修改。软件会保留这些内容;再次点击“应用配置修改”后才会重新写入软件负责的字段。" - .to_string(), - ], - }, - Err(error) => AgentModificationInspection { - enabled: false, - state: "invalid".to_string(), - backup_available: false, - applied_model: Some(state.model), - warnings: vec![error], - }, + Ok(Some(state)) => AgentModificationInspection { + enabled: true, + state: "applied".to_string(), + backup_available: false, + applied_model: Some(state.model), + warnings: Vec::new(), }, - Ok(None) => match agent_managed_fingerprint(client, home) { - Ok(_) => AgentModificationInspection { - enabled: false, - state: "unconfigured".to_string(), - backup_available: false, - applied_model: None, - warnings: Vec::new(), - }, - Err(error) => AgentModificationInspection { - enabled: false, - state: "invalid".to_string(), - backup_available: false, - applied_model: None, - warnings: vec![error], - }, + Ok(None) => AgentModificationInspection { + enabled: false, + state: "unconfigured".to_string(), + backup_available: false, + applied_model: None, + warnings: Vec::new(), }, Err(error) => AgentModificationInspection { enabled: false, @@ -4601,320 +4566,11 @@ fn sha256_bytes(bytes: &[u8]) -> String { format!("{:x}", Sha256::digest(bytes)) } -fn optional_json_object(content: Option<&str>, label: &str) -> Result { - let Some(content) = content.map(str::trim).filter(|content| !content.is_empty()) else { - return Ok(serde_json::json!({})); - }; - let value = serde_json::from_str::(content) - .map_err(|error| format!("{label} 格式无效: {error}"))?; - if value.is_object() { - Ok(value) - } else { - Err(format!("{label} 根节点必须是对象")) - } -} - -fn json_field(value: &serde_json::Value, key: &str) -> serde_json::Value { - value.get(key).cloned().unwrap_or(serde_json::Value::Null) -} - -fn agent_managed_projection( - client: AgentClient, - contents: &[Option], -) -> Result { - match client { - AgentClient::ClaudeCode => { - let root = optional_json_object( - contents.first().and_then(Option::as_deref), - "Claude Code 配置", - )?; - let env = root.get("env").cloned().unwrap_or(serde_json::Value::Null); - let managed_env = [ - "ANTHROPIC_BASE_URL", - "ANTHROPIC_API_KEY", - "ANTHROPIC_AUTH_TOKEN", - "ANTHROPIC_MODEL", - "ANTHROPIC_DEFAULT_HAIKU_MODEL", - "ANTHROPIC_DEFAULT_SONNET_MODEL", - "ANTHROPIC_DEFAULT_OPUS_MODEL", - "ANTHROPIC_DEFAULT_FABLE_MODEL", - ] - .into_iter() - .map(|key| (key.to_string(), json_field(&env, key))) - .collect::>(); - Ok(serde_json::json!({ - "env": managed_env, - "model": json_field(&root, "model"), - })) - } - AgentClient::ClaudeDesktop => { - if contents.len() < 4 { - return Err("Claude Desktop 配置文件数量不完整".to_string()); - } - let normal = optional_json_object(contents[0].as_deref(), "Claude Desktop 主配置")?; - let threep = optional_json_object(contents[1].as_deref(), "Claude Desktop 3P 配置")?; - let profile = optional_json_object(contents[2].as_deref(), "Claude Desktop 网关配置")?; - let meta = optional_json_object(contents[3].as_deref(), "Claude Desktop 配置索引")?; - let profile_fields = [ - "coworkEgressAllowedHosts", - "disableDeploymentModeChooser", - "inferenceGatewayApiKey", - "inferenceGatewayAuthScheme", - "inferenceGatewayBaseUrl", - "inferenceProvider", - "inferenceModels", - ] - .into_iter() - .map(|key| (key.to_string(), json_field(&profile, key))) - .collect::>(); - let managed_entries = meta - .get("entries") - .and_then(serde_json::Value::as_array) - .map(|entries| { - entries - .iter() - .filter(|entry| { - entry.get("id").and_then(serde_json::Value::as_str) - == Some(CLAUDE_DESKTOP_PROFILE_ID) - }) - .map(|entry| { - serde_json::json!({ - "id": json_field(entry, "id"), - "name": json_field(entry, "name"), - }) - }) - .collect::>() - }) - .map(serde_json::Value::Array) - .unwrap_or_else(|| json_field(&meta, "entries")); - Ok(serde_json::json!({ - "normalDeploymentMode": json_field(&normal, "deploymentMode"), - "threepDeploymentMode": json_field(&threep, "deploymentMode"), - "profile": profile_fields, - "meta": { - "appliedId": json_field(&meta, "appliedId"), - "entries": managed_entries, - }, - })) - } - AgentClient::Codex => { - let content = contents - .first() - .and_then(Option::as_deref) - .unwrap_or_default(); - let root = if content.trim().is_empty() { - toml::Value::Table(toml::map::Map::new()) - } else { - toml::from_str::(content) - .map_err(|error| format!("Codex config.toml 格式无效: {error}"))? - }; - let provider = root - .get("model_providers") - .and_then(toml::Value::as_table) - .and_then(|providers| providers.get(MANAGED_AGENT_PROVIDER_ID)); - let provider_projection = [ - "name", - "base_url", - "wire_api", - "experimental_bearer_token", - "requires_openai_auth", - ] - .into_iter() - .map(|key| { - let value = provider - .and_then(|provider| provider.get(key)) - .and_then(|value| serde_json::to_value(value).ok()) - .unwrap_or(serde_json::Value::Null); - (key.to_string(), value) - }) - .collect::>(); - let root_value = |key: &str| { - root.get(key) - .and_then(|value| serde_json::to_value(value).ok()) - .unwrap_or(serde_json::Value::Null) - }; - let catalog_sha256 = contents - .get(1) - .and_then(Option::as_deref) - .map(|content| serde_json::Value::String(sha256_bytes(content.as_bytes()))) - .unwrap_or(serde_json::Value::Null); - Ok(serde_json::json!({ - "model_provider": root_value("model_provider"), - "model": root_value("model"), - "model_catalog_json": root_value("model_catalog_json"), - "provider": provider_projection, - "catalog_sha256": catalog_sha256, - })) - } - AgentClient::OpenCode => { - let root = - optional_json_object(contents.first().and_then(Option::as_deref), "OpenCode 配置")?; - let provider = root - .get("provider") - .and_then(|providers| providers.get(MANAGED_AGENT_PROVIDER_ID)) - .cloned() - .unwrap_or(serde_json::Value::Null); - let options = provider - .get("options") - .cloned() - .unwrap_or(serde_json::Value::Null); - Ok(serde_json::json!({ - "model": json_field(&root, "model"), - "provider": { - "npm": json_field(&provider, "npm"), - "name": json_field(&provider, "name"), - "options": { - "baseURL": json_field(&options, "baseURL"), - "apiKey": json_field(&options, "apiKey"), - }, - "models": json_field(&provider, "models"), - }, - })) - } - AgentClient::OpenClaw => { - let content = contents - .first() - .and_then(Option::as_deref) - .unwrap_or_default(); - let root = if content.trim().is_empty() { - serde_json::json!({}) - } else { - let value = json5::from_str::(content) - .map_err(|error| format!("OpenClaw 配置格式无效: {error}"))?; - if !value.is_object() { - return Err("OpenClaw 配置根节点必须是对象".to_string()); - } - value - }; - let provider = root - .pointer(&format!("/models/providers/{MANAGED_AGENT_PROVIDER_ID}")) - .cloned() - .unwrap_or(serde_json::Value::Null); - let defaults = root - .pointer("/agents/defaults") - .cloned() - .unwrap_or_default(); - let prefix = format!("{MANAGED_AGENT_PROVIDER_ID}/"); - let managed_models = defaults - .get("models") - .and_then(serde_json::Value::as_object) - .map(|models| { - models - .iter() - .filter(|(name, _)| name.starts_with(&prefix)) - .map(|(name, value)| (name.clone(), value.clone())) - .collect::>() - }) - .map(serde_json::Value::Object) - .unwrap_or_else(|| { - defaults - .get("models") - .cloned() - .unwrap_or(serde_json::Value::Null) - }); - Ok(serde_json::json!({ - "provider": { - "baseUrl": json_field(&provider, "baseUrl"), - "apiKey": json_field(&provider, "apiKey"), - "api": json_field(&provider, "api"), - "models": json_field(&provider, "models"), - }, - "primary": defaults.pointer("/model/primary").cloned().unwrap_or(serde_json::Value::Null), - "models": managed_models, - })) - } - AgentClient::Hermes => { - let content = contents - .first() - .and_then(Option::as_deref) - .unwrap_or_default(); - let root = if content.trim().is_empty() { - serde_yaml::Value::Mapping(serde_yaml::Mapping::new()) - } else { - serde_yaml::from_str::(content) - .map_err(|error| format!("Hermes 配置格式无效: {error}"))? - }; - if !root.is_mapping() { - return Err("Hermes 配置根节点必须是映射".to_string()); - } - let providers = if let Some(providers) = root - .get("custom_providers") - .and_then(serde_yaml::Value::as_sequence) - { - let providers = providers - .iter() - .filter(|provider| { - provider.get("name").and_then(serde_yaml::Value::as_str) - == Some(MANAGED_AGENT_PROVIDER_ID) - }) - .map(|provider| { - let provider = serde_json::to_value(provider).map_err(|error| { - format!("序列化 Hermes 受管 Provider 失败: {error}") - })?; - Ok(serde_json::json!({ - "name": json_field(&provider, "name"), - "base_url": json_field(&provider, "base_url"), - "api_key": json_field(&provider, "api_key"), - "api_mode": json_field(&provider, "api_mode"), - "model": json_field(&provider, "model"), - "models": json_field(&provider, "models"), - })) - }) - .collect::, String>>()?; - serde_json::Value::Array(providers) - } else { - serde_json::to_value( - root.get("custom_providers") - .cloned() - .unwrap_or(serde_yaml::Value::Null), - ) - .map_err(|error| format!("序列化 Hermes 受管配置失败: {error}"))? - }; - let model = serde_json::to_value( - root.get("model") - .cloned() - .unwrap_or(serde_yaml::Value::Null), - ) - .map_err(|error| format!("序列化 Hermes 模型配置失败: {error}"))?; - Ok(serde_json::json!({ - "providers": providers, - "model": { - "default": json_field(&model, "default"), - "provider": json_field(&model, "provider"), - }, - })) - } - } -} - fn agent_managed_paths(client: AgentClient, home: &Path) -> Vec { let paths = agent_config_paths(client, home); expected_agent_record_paths(client, &paths) } -fn read_agent_managed_contents(paths: &[PathBuf]) -> Result>, String> { - paths - .iter() - .map(|path| { - let Some(bytes) = read_agent_bytes(path)? else { - return Ok(None); - }; - String::from_utf8(bytes) - .map(Some) - .map_err(|_| format!("智能体配置不是 UTF-8 文本: {}", path_to_string(path))) - }) - .collect() -} - -fn agent_managed_fingerprint(client: AgentClient, home: &Path) -> Result { - let paths = agent_managed_paths(client, home); - let contents = read_agent_managed_contents(&paths)?; - let projection = agent_managed_projection(client, &contents)?; - let bytes = serde_json::to_vec(&projection) - .map_err(|error| format!("生成智能体受管字段指纹失败: {error}"))?; - Ok(sha256_bytes(&bytes)) -} - fn legacy_agent_backup_paths(state_content: &str) -> Vec { serde_json::from_str::(state_content) .ok() @@ -4973,7 +4629,6 @@ fn load_agent_applied_state( version: AGENT_APPLIED_STATE_VERSION, client: client.id().to_string(), model: record.model, - managed_fingerprint: agent_managed_fingerprint(client, home)?, updated_at_unix: SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() @@ -5865,7 +5520,6 @@ fn commit_agent_configuration( version: AGENT_APPLIED_STATE_VERSION, client: client.id().to_string(), model: model.to_string(), - managed_fingerprint: agent_managed_fingerprint(client, home)?, updated_at_unix: SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() @@ -13889,7 +13543,7 @@ mod tests { .unwrap() .exists()); assert_eq!( - inspect_agent_application(AgentClient::Codex, &home, true).state, + inspect_agent_application(AgentClient::Codex, &home).state, "applied" ); @@ -13897,15 +13551,15 @@ mod tests { document["user_setting"] = toml_edit::value("changed-externally"); fs::write(&path, document.to_string()).unwrap(); assert_eq!( - inspect_agent_application(AgentClient::Codex, &home, true).state, + inspect_agent_application(AgentClient::Codex, &home).state, "applied" ); document["model"] = toml_edit::value("external-model"); fs::write(&path, document.to_string()).unwrap(); assert_eq!( - inspect_agent_application(AgentClient::Codex, &home, false).state, - "external-changed" + inspect_agent_application(AgentClient::Codex, &home).state, + "applied" ); apply_agent_configuration( @@ -13987,7 +13641,7 @@ mod tests { assert_eq!(root["model"], "cpa-gui/model-a"); assert!(!agent_backup_path(&path).unwrap().exists()); assert_eq!( - inspect_agent_application(AgentClient::OpenCode, &home, true).state, + inspect_agent_application(AgentClient::OpenCode, &home).state, "applied" ); fs::remove_dir_all(home).unwrap(); @@ -14485,44 +14139,6 @@ mod tests { ); } - #[test] - fn agent_managed_projection_ignores_unmanaged_provider_extensions() { - let opencode_a = vec![Some( - r#"{"model":"cpa-gui/gpt-test","provider":{"cpa-gui":{"npm":"sdk","name":"CPA","options":{"baseURL":"http://localhost","apiKey":"key","timeout":10},"models":{},"custom":"a"}}}"#.to_string(), - )]; - let opencode_b = vec![Some( - r#"{"model":"cpa-gui/gpt-test","provider":{"cpa-gui":{"npm":"sdk","name":"CPA","options":{"baseURL":"http://localhost","apiKey":"key","timeout":99},"models":{},"custom":"b"}}}"#.to_string(), - )]; - assert_eq!( - agent_managed_projection(AgentClient::OpenCode, &opencode_a).unwrap(), - agent_managed_projection(AgentClient::OpenCode, &opencode_b).unwrap() - ); - - let openclaw_a = vec![Some( - r#"{"models":{"providers":{"cpa-gui":{"baseUrl":"http://localhost","apiKey":"key","api":"openai-completions","models":[],"custom":"a"}}},"agents":{"defaults":{"model":{"primary":"cpa-gui/gpt-test"},"models":{}}}}"#.to_string(), - )]; - let openclaw_b = vec![Some( - r#"{"models":{"providers":{"cpa-gui":{"baseUrl":"http://localhost","apiKey":"key","api":"openai-completions","models":[],"custom":"b"}}},"agents":{"defaults":{"model":{"primary":"cpa-gui/gpt-test"},"models":{}}}}"#.to_string(), - )]; - assert_eq!( - agent_managed_projection(AgentClient::OpenClaw, &openclaw_a).unwrap(), - agent_managed_projection(AgentClient::OpenClaw, &openclaw_b).unwrap() - ); - - let hermes_a = vec![Some( - "custom_providers:\n - name: cpa-gui\n base_url: http://localhost\n api_key: key\n api_mode: chat_completions\n model: gpt-test\n models: {}\n custom: a\nmodel:\n default: gpt-test\n provider: cpa-gui\n extension: a\n" - .to_string(), - )]; - let hermes_b = vec![Some( - "custom_providers:\n - name: cpa-gui\n base_url: http://localhost\n api_key: key\n api_mode: chat_completions\n model: gpt-test\n models: {}\n custom: b\nmodel:\n default: gpt-test\n provider: cpa-gui\n extension: b\n" - .to_string(), - )]; - assert_eq!( - agent_managed_projection(AgentClient::Hermes, &hermes_a).unwrap(), - agent_managed_projection(AgentClient::Hermes, &hermes_b).unwrap() - ); - } - #[test] fn agent_model_list_parser_accepts_core_response_and_deduplicates_ids() { let models = parse_agent_model_options(&serde_json::json!({ @@ -16469,10 +16085,7 @@ custom_option = "keep-original" assert!( validate_agent_launch_modification(AgentClient::Codex, false, "unconfigured").is_ok() ); - assert!( - validate_agent_launch_modification(AgentClient::Codex, true, "external-changed",) - .is_ok() - ); + assert!(validate_agent_launch_modification(AgentClient::Codex, true, "invalid",).is_ok()); assert!( validate_agent_launch_modification(AgentClient::OpenCode, false, "unconfigured") .is_err() diff --git a/src/i18n/ja.ts b/src/i18n/ja.ts index 0066ebd..9bef28d 100644 --- a/src/i18n/ja.ts +++ b/src/i18n/ja.ts @@ -426,7 +426,6 @@ export const jaOverrides = { 'agents.status.conflict': '設定競合', 'agents.status.modified': '設定変更済み', 'agents.status.original': '元の設定を維持', - 'agents.status.externalChanged': '外部設定は適用待ち', 'agents.status.invalid': '設定が無効', 'agents.status.applied': '適用済み', 'agents.status.unconfigured': '未設定', @@ -520,12 +519,10 @@ export const jaOverrides = { 'agents.modify.apply': '設定変更を適用', 'agents.modify.default': 'デフォルト設定', 'agents.modify.clear': '設定を消去', - 'agents.modify.externalChanged': '外部の設定変更を検出し、その内容を保持しています。「設定変更を適用」をクリックするまで管理対象フィールドは書き換えません。', 'agents.modify.invalid': '現在の設定を解析できません。「設定変更を適用」は最小の有効な設定を再構築し、「デフォルト設定」はファイル全体をリセットします。', 'agents.modify.applied': 'アプリが管理するフィールドは適用済みで、それ以外のユーザー設定は保持されています。', 'agents.footer.changed': 'モデル選択が変更されました。設定変更を適用すると新しいモデルが使用されます。', 'agents.footer.applied': '設定変更は適用済みです。アプリは関連フィールドだけを管理します。', - 'agents.footer.externalChanged': '外部変更を保持しています。設定変更を適用すると管理対象フィールドが再度書き込まれます。', 'agents.footer.invalidManaged': '設定が無効です。変更を適用して修復するか、デフォルト設定にリセットしてください。', 'agents.footer.unsupported': '現在のシステムではこのクライアントを設定できません', 'agents.footer.installFirst': 'クライアントをインストールしてから再検出してください', diff --git a/src/i18n/resources.ts b/src/i18n/resources.ts index b2c837c..1a287aa 100644 --- a/src/i18n/resources.ts +++ b/src/i18n/resources.ts @@ -429,7 +429,6 @@ export const zhCN = { 'agents.status.conflict': '配置冲突', 'agents.status.modified': '已修改配置', 'agents.status.original': '保持原配置', - 'agents.status.externalChanged': '外部配置待应用', 'agents.status.invalid': '配置无效', 'agents.status.applied': '已应用', 'agents.status.unconfigured': '尚未配置', @@ -523,12 +522,10 @@ export const zhCN = { 'agents.modify.apply': '应用配置修改', 'agents.modify.default': '默认配置', 'agents.modify.clear': '清空配置', - 'agents.modify.externalChanged': '检测到外部配置变化。软件会保留这些内容;点击“应用配置修改”后才会重新写入受管字段。', 'agents.modify.invalid': '当前配置无法解析。点击“应用配置修改”会重建最小合法配置;“默认配置”会重置整个文件。', 'agents.modify.applied': '软件负责的配置字段已应用,其他用户内容保持不变。', 'agents.footer.changed': '模型选择已变化;应用配置修改后将使用新模型。', 'agents.footer.applied': '配置修改已应用;软件只管理相关字段。', - 'agents.footer.externalChanged': '外部配置已保留;应用配置修改后才会重新写入受管字段。', 'agents.footer.invalidManaged': '配置当前无效,请应用修改进行修复,或重置为默认配置。', 'agents.footer.unsupported': '当前系统无法配置该客户端', 'agents.footer.installFirst': '请先安装客户端并重新检测', @@ -1277,7 +1274,6 @@ export const en: Record = { 'agents.status.conflict': 'Configuration Conflict', 'agents.status.modified': 'Configuration Modified', 'agents.status.original': 'Original Configuration', - 'agents.status.externalChanged': 'External Changes Pending', 'agents.status.invalid': 'Invalid Configuration', 'agents.status.applied': 'Applied', 'agents.status.unconfigured': 'Not Configured', @@ -1371,12 +1367,10 @@ export const en: Record = { 'agents.modify.apply': 'Apply Configuration Changes', 'agents.modify.default': 'Default Configuration', 'agents.modify.clear': 'Clear Configuration', - 'agents.modify.externalChanged': 'External configuration changes were detected and preserved. Managed fields are rewritten only after you click Apply Configuration Changes.', 'agents.modify.invalid': 'The current configuration cannot be parsed. Apply Configuration Changes rebuilds a minimal valid configuration; Default Configuration resets the whole file.', 'agents.modify.applied': 'The fields managed by the app are applied; all other user content remains unchanged.', 'agents.footer.changed': 'The model selection changed. Apply the configuration to use the new model.', 'agents.footer.applied': 'Configuration changes are applied; only the relevant fields are managed by the app.', - 'agents.footer.externalChanged': 'External changes were preserved. Managed fields are rewritten when you apply the configuration.', 'agents.footer.invalidManaged': 'The configuration is invalid. Apply changes to repair it or reset it to the default configuration.', 'agents.footer.unsupported': 'This client cannot be configured on the current system', 'agents.footer.installFirst': 'Install the client, then detect again', diff --git a/src/pages/AgentsPage.tsx b/src/pages/AgentsPage.tsx index d9d58bd..4cee53d 100644 --- a/src/pages/AgentsPage.tsx +++ b/src/pages/AgentsPage.tsx @@ -49,7 +49,7 @@ type AgentClientId = | 'openclaw' | 'hermes'; -type AgentModificationState = 'unconfigured' | 'applied' | 'external-changed' | 'invalid'; +type AgentModificationState = 'unconfigured' | 'applied' | 'invalid'; type AgentConfigStatus = { id: AgentClientId; @@ -234,7 +234,6 @@ const listStatusText = (status: AgentConfigStatus | undefined) => { if (!status) return translate(locale, 'agents.list.detecting'); if (!status.supportedPlatform) return translate(locale, 'agents.list.unsupported'); if (!status.installed) return translate(locale, 'agents.list.notInstalled'); - if (status.modificationState === 'external-changed') return translate(locale, 'agents.status.externalChanged'); if (status.modificationState === 'invalid') return translate(locale, 'agents.status.invalid'); if (status.modificationState === 'applied') return translate(locale, 'agents.list.modified', { model: status.appliedModel ?? '—' }); return status.version @@ -657,28 +656,24 @@ export function AgentsPage() { : activeStatus?.modificationState === 'applied' ? t('agents.model.current', { model: appliedModel || '—' }) : t('agents.model.firstSelection', { count: models.length })); - const modificationDescription = activeStatus?.modificationState === 'external-changed' - ? t('agents.modify.externalChanged') - : activeStatus?.modificationState === 'invalid' - ? t('agents.modify.invalid') - : activeStatus?.modificationState === 'applied' - ? t('agents.modify.applied') - : ''; + const modificationDescription = activeStatus?.modificationState === 'invalid' + ? t('agents.modify.invalid') + : activeStatus?.modificationState === 'applied' + ? t('agents.modify.applied') + : ''; const footerMessage = activeStatus?.modificationState === 'applied' ? draftChanged ? t('agents.footer.changed') : t('agents.footer.applied') - : activeStatus?.modificationState === 'external-changed' - ? t('agents.footer.externalChanged') - : activeStatus?.modificationState === 'invalid' - ? t('agents.footer.invalidManaged') - : !activeStatus?.supportedPlatform - ? t('agents.footer.unsupported') - : !activeStatus.installed - ? t('agents.footer.installFirst') - : activeStatus.launchTargets.length === 0 - ? t('agents.footer.noCommand') - : ''; + : activeStatus?.modificationState === 'invalid' + ? t('agents.footer.invalidManaged') + : !activeStatus?.supportedPlatform + ? t('agents.footer.unsupported') + : !activeStatus.installed + ? t('agents.footer.installFirst') + : activeStatus.launchTargets.length === 0 + ? t('agents.footer.noCommand') + : ''; const refreshModels = () => { void loadModels();