Add agent configuration status caching and force-refresh support

This commit is contained in:
Luis Pater committed 2026-07-19 23:17:42 +08:00
1 parent 801685427f
commit 40bfd422a4
2 files changed
+142 -13

No files matched your search

+128 -6
View File
@@ -98,6 +98,49 @@ struct GuiConfigState {
inner: Mutex<GuiConfigFile>,
}
#[derive(Default)]
struct AgentConfigStatusCache {
refresh_lock: Mutex<()>,
entry: Mutex<Option<AgentConfigStatusCacheEntry>>,
}
struct AgentConfigStatusCacheEntry {
port: u16,
statuses: Vec<AgentConfigStatus>,
}
impl AgentConfigStatusCache {
fn get(&self, port: u16) -> Result<Option<Vec<AgentConfigStatus>>, String> {
self.entry
.lock()
.map(|entry| {
entry
.as_ref()
.filter(|entry| entry.port == port)
.map(|entry| entry.statuses.clone())
})
.map_err(|_| "智能体配置状态缓存锁已损坏".to_string())
}
fn replace(&self, port: u16, statuses: Vec<AgentConfigStatus>) -> Result<(), String> {
let mut current = self
.entry
.lock()
.map_err(|_| "智能体配置状态缓存锁已损坏".to_string())?;
*current = Some(AgentConfigStatusCacheEntry { port, statuses });
Ok(())
}
fn clear(&self) -> Result<(), String> {
let mut current = self
.entry
.lock()
.map_err(|_| "智能体配置状态缓存锁已损坏".to_string())?;
*current = None;
Ok(())
}
}
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct CorePlatform {
@@ -864,16 +907,14 @@ fn get_gui_settings(
Ok(GuiSettings::from(&config))
}
#[tauri::command]
fn get_agent_config_statuses(
app: tauri::AppHandle,
gui_config_state: tauri::State<'_, GuiConfigState>,
fn inspect_agent_config_statuses(
app: &tauri::AppHandle,
port: u16,
) -> Result<Vec<AgentConfigStatus>, String> {
let home = app
.path()
.home_dir()
.map_err(|error| format!("无法获取用户目录: {error}"))?;
let port = gui_config_state.snapshot()?.port;
Ok([
AgentClient::ClaudeCode,
AgentClient::ClaudeDesktop,
@@ -887,6 +928,49 @@ fn get_agent_config_statuses(
.collect())
}
fn refresh_agent_config_status_cache(
app: &tauri::AppHandle,
gui_config_state: &GuiConfigState,
cache: &AgentConfigStatusCache,
) -> Result<Vec<AgentConfigStatus>, String> {
let _refresh_guard = cache
.refresh_lock
.lock()
.map_err(|_| "智能体配置状态刷新锁已损坏".to_string())?;
let port = gui_config_state.snapshot()?.port;
let statuses = inspect_agent_config_statuses(app, port)?;
cache.replace(port, statuses.clone())?;
Ok(statuses)
}
#[tauri::command]
fn get_agent_config_statuses(
app: tauri::AppHandle,
gui_config_state: tauri::State<'_, GuiConfigState>,
cache: tauri::State<'_, AgentConfigStatusCache>,
) -> Result<Vec<AgentConfigStatus>, String> {
{
let _refresh_guard = cache
.refresh_lock
.lock()
.map_err(|_| "智能体配置状态刷新锁已损坏".to_string())?;
let port = gui_config_state.snapshot()?.port;
if let Some(statuses) = cache.get(port)? {
return Ok(statuses);
}
}
refresh_agent_config_status_cache(&app, gui_config_state.inner(), cache.inner())
}
#[tauri::command]
fn refresh_agent_config_statuses(
app: tauri::AppHandle,
gui_config_state: tauri::State<'_, GuiConfigState>,
cache: tauri::State<'_, AgentConfigStatusCache>,
) -> Result<Vec<AgentConfigStatus>, String> {
refresh_agent_config_status_cache(&app, gui_config_state.inner(), cache.inner())
}
#[tauri::command]
async fn get_agent_models(
gui_config_state: tauri::State<'_, GuiConfigState>,
@@ -4254,12 +4338,17 @@ fn detect_lan_ipv4() -> Option<Ipv4Addr> {
#[tauri::command]
fn save_gui_settings(
gui_config_state: tauri::State<'_, GuiConfigState>,
cache: tauri::State<'_, AgentConfigStatusCache>,
settings: GuiNetworkSettings,
) -> Result<GuiSettings, String> {
if settings.port == 0 {
return Err("端口必须在 1 到 65535 之间".to_string());
}
let _refresh_guard = cache
.refresh_lock
.lock()
.map_err(|_| "智能体配置状态刷新锁已损坏".to_string())?;
let previous = gui_config_state.snapshot()?;
let mut next = previous.clone();
next.port = settings.port;
@@ -4278,6 +4367,12 @@ fn save_gui_settings(
}
};
if config.port != previous.port {
if let Err(error) = cache.clear() {
eprintln!("清空智能体配置状态缓存失败: {error}");
}
}
Ok(GuiSettings::from(&config))
}
@@ -8797,7 +8892,8 @@ fn main() {
.manage(CoreDownloadState::default())
.manage(CoreProcessState::default())
.manage(usage::UsageCollectorState::default())
.manage(GuiConfigState::new(gui_config));
.manage(GuiConfigState::new(gui_config))
.manage(AgentConfigStatusCache::default());
#[cfg(target_os = "macos")]
let app = app.on_window_event(|window, event| {
@@ -8828,6 +8924,19 @@ fn main() {
usage::start_usage_collector(usage_app);
});
let agent_status_app = app.handle().clone();
tauri::async_runtime::spawn_blocking(move || {
let gui_config_state = agent_status_app.state::<GuiConfigState>();
let cache = agent_status_app.state::<AgentConfigStatusCache>();
if let Err(error) = refresh_agent_config_status_cache(
&agent_status_app,
gui_config_state.inner(),
cache.inner(),
) {
eprintln!("后台刷新智能体配置状态失败: {error}");
}
});
let core_app = app.handle().clone();
tauri::async_runtime::spawn_blocking(move || {
let gui_config_state = core_app.state::<GuiConfigState>();
@@ -8857,6 +8966,7 @@ fn main() {
get_core_status,
get_gui_settings,
get_agent_config_statuses,
refresh_agent_config_statuses,
get_agent_models,
get_thinking_aliases,
get_thinking_alias_sources,
@@ -8913,6 +9023,18 @@ fn main() {
mod tests {
use super::*;
#[test]
fn agent_status_cache_requires_matching_port() {
let cache = AgentConfigStatusCache::default();
cache.replace(8317, Vec::new()).unwrap();
assert!(cache.get(8317).unwrap().is_some_and(|statuses| statuses.is_empty()));
assert!(cache.get(8318).unwrap().is_none());
cache.clear().unwrap();
assert!(cache.get(8317).unwrap().is_none());
}
fn agent_test_home(name: &str) -> PathBuf {
let path = std::env::temp_dir().join(format!(
"cpa-gui-agent-{name}-{}-{}",
+14 -7
View File
@@ -462,8 +462,11 @@ export function AgentsPage() {
const [notice, setNotice] = useState('');
const [restoreConflict, setRestoreConflict] = useState<string[] | null>(null);
const loadStatuses = useCallback(async () => {
const nextStatuses = await invoke<AgentConfigStatus[]>('get_agent_config_statuses');
const loadStatuses = useCallback(async (forceRefresh = false) => {
const command = forceRefresh
? 'refresh_agent_config_statuses'
: 'get_agent_config_statuses';
const nextStatuses = await invoke<AgentConfigStatus[]>(command);
setStatuses(nextStatuses);
}, []);
@@ -489,7 +492,7 @@ export function AgentsPage() {
setLoading(true);
setError('');
try {
await Promise.all([loadStatuses(), loadModels()]);
await Promise.all([loadStatuses(true), loadModels()]);
} catch (requestError) {
setError(String(requestError));
} finally {
@@ -498,8 +501,12 @@ export function AgentsPage() {
}, [loadModels, loadStatuses]);
useEffect(() => {
void refresh();
}, [refresh]);
setLoading(true);
setError('');
void Promise.all([loadStatuses(), loadModels()])
.catch((requestError) => setError(String(requestError)))
.finally(() => setLoading(false));
}, [loadModels, loadStatuses]);
useEffect(() => {
writeSelectedAgentClient(selected);
@@ -603,7 +610,7 @@ export function AgentsPage() {
setNotice(result.enabled
? `${activeDefinition.name} 已启用配置修改;退出软件前建议关闭“修改智能体配置”,恢复原配置文件`
: `${activeDefinition.name} 的原配置已恢复,请重启客户端`);
await loadStatuses();
await loadStatuses(true);
} catch (requestError) {
setError(String(requestError));
} finally {
@@ -640,7 +647,7 @@ export function AgentsPage() {
: activeStatus?.modificationEnabled
? `${activeDefinition.name} 已启动;退出软件前建议关闭“修改智能体配置”,恢复原配置文件`
: `${activeDefinition.name} 已启动`);
if (appliedBeforeLaunch) await loadStatuses();
if (appliedBeforeLaunch) await loadStatuses(true);
} catch (requestError) {
setError(String(requestError));
} finally {