diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index cae9b1a..9483141 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -224,6 +224,7 @@ fn has_supervised_channels(config: &Config) -> bool { irc, lark, dingtalk, + linq, qq, .. } = &config.channels_config; @@ -240,6 +241,7 @@ fn has_supervised_channels(config: &Config) -> bool { || irc.is_some() || lark.is_some() || dingtalk.is_some() + || linq.is_some() || qq.is_some() } diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 9847806..b28ed1a 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -653,11 +653,16 @@ async fn persist_pairing_tokens(config: Arc>, pairing: &PairingGua let paired_tokens = pairing.tokens(); // This is needed because parking_lot's guard is not Send so we clone the inner // this should be removed once async mutexes are used everywhere - let mut cfg = { config.lock().clone() }; - cfg.gateway.paired_tokens = paired_tokens; - cfg.save() + let mut updated_cfg = { config.lock().clone() }; + updated_cfg.gateway.paired_tokens = paired_tokens; + updated_cfg + .save() .await - .context("Failed to persist paired tokens to config.toml") + .context("Failed to persist paired tokens to config.toml")?; + + // Keep shared runtime config in sync with persisted tokens. + *config.lock() = updated_cfg; + Ok(()) } /// Webhook request body @@ -1410,7 +1415,9 @@ mod tests { assert!(guard.is_authenticated(&token)); let shared_config = Arc::new(Mutex::new(config)); - persist_pairing_tokens(shared_config, &guard).await.unwrap(); + persist_pairing_tokens(shared_config.clone(), &guard) + .await + .unwrap(); let saved = tokio::fs::read_to_string(config_path).await.unwrap(); let parsed: Config = toml::from_str(&saved).unwrap(); @@ -1418,6 +1425,10 @@ mod tests { let persisted = &parsed.gateway.paired_tokens[0]; assert_eq!(persisted.len(), 64); assert!(persisted.chars().all(|c| c.is_ascii_hexdigit())); + + let in_memory = shared_config.lock(); + assert_eq!(in_memory.gateway.paired_tokens.len(), 1); + assert_eq!(&in_memory.gateway.paired_tokens[0], persisted); } #[test] diff --git a/src/onboard/wizard.rs b/src/onboard/wizard.rs index 8522540..0aa5a66 100644 --- a/src/onboard/wizard.rs +++ b/src/onboard/wizard.rs @@ -74,6 +74,7 @@ fn has_launchable_channels(channels: &ChannelsConfig) -> bool { irc, lark, dingtalk, + linq, qq, .. } = channels; @@ -90,6 +91,7 @@ fn has_launchable_channels(channels: &ChannelsConfig) -> bool { || irc.is_some() || lark.is_some() || dingtalk.is_some() + || linq.is_some() || qq.is_some() } diff --git a/src/tools/proxy_config.rs b/src/tools/proxy_config.rs index 5f4183d..a4d90d1 100644 --- a/src/tools/proxy_config.rs +++ b/src/tools/proxy_config.rs @@ -189,19 +189,40 @@ impl ProxyConfigTool { })?; } - if let MaybeSet::Set(update) = Self::parse_optional_string_update(args, "http_proxy")? { - proxy.http_proxy = Some(update); - touched_proxy_url = true; + match Self::parse_optional_string_update(args, "http_proxy")? { + MaybeSet::Set(update) => { + proxy.http_proxy = Some(update); + touched_proxy_url = true; + } + MaybeSet::Null => { + proxy.http_proxy = None; + touched_proxy_url = true; + } + MaybeSet::Unset => {} } - if let MaybeSet::Set(update) = Self::parse_optional_string_update(args, "https_proxy")? { - proxy.https_proxy = Some(update); - touched_proxy_url = true; + match Self::parse_optional_string_update(args, "https_proxy")? { + MaybeSet::Set(update) => { + proxy.https_proxy = Some(update); + touched_proxy_url = true; + } + MaybeSet::Null => { + proxy.https_proxy = None; + touched_proxy_url = true; + } + MaybeSet::Unset => {} } - if let MaybeSet::Set(update) = Self::parse_optional_string_update(args, "all_proxy")? { - proxy.all_proxy = Some(update); - touched_proxy_url = true; + match Self::parse_optional_string_update(args, "all_proxy")? { + MaybeSet::Set(update) => { + proxy.all_proxy = Some(update); + touched_proxy_url = true; + } + MaybeSet::Null => { + proxy.all_proxy = None; + touched_proxy_url = true; + } + MaybeSet::Unset => {} } if let Some(no_proxy_raw) = args.get("no_proxy") { @@ -494,4 +515,34 @@ mod tests { assert!(get_result.output.contains("provider.openai")); assert!(get_result.output.contains("services")); } + + #[tokio::test] + async fn set_null_proxy_url_clears_existing_value() { + let tmp = TempDir::new().unwrap(); + let tool = ProxyConfigTool::new(test_config(&tmp).await, test_security()); + + let set_result = tool + .execute(json!({ + "action": "set", + "http_proxy": "http://127.0.0.1:7890" + })) + .await + .unwrap(); + assert!(set_result.success, "{:?}", set_result.error); + + let clear_result = tool + .execute(json!({ + "action": "set", + "http_proxy": null + })) + .await + .unwrap(); + assert!(clear_result.success, "{:?}", clear_result.error); + + let get_result = tool.execute(json!({"action": "get"})).await.unwrap(); + assert!(get_result.success); + let parsed: Value = serde_json::from_str(&get_result.output).unwrap(); + assert!(parsed["proxy"]["http_proxy"].is_null()); + assert!(parsed["runtime_proxy"]["http_proxy"].is_null()); + } }