huskies: merge 898

This commit is contained in:
dave
2026-05-12 21:29:04 +00:00
parent d78dd9e8f9
commit 937792f208
10 changed files with 829 additions and 23 deletions
+21 -14
View File
@@ -21,13 +21,23 @@ pub fn load_config(path: &Path) -> Result<GatewayConfig, String> {
/// Persist the current projects map to `<config_dir>/projects.toml`.
/// Silently ignores write errors or skips when `config_dir` is empty.
///
/// Existing `[sled_tokens]` entries are preserved so that adding or removing
/// projects via the UI does not wipe the sled authentication tokens.
pub async fn save_config(projects: &BTreeMap<String, ProjectEntry>, config_dir: &Path) {
if config_dir.as_os_str().is_empty() {
return;
}
let path = config_dir.join("projects.toml");
let sled_tokens = tokio::fs::read_to_string(&path)
.await
.ok()
.and_then(|data| toml::from_str::<GatewayConfig>(&data).ok())
.map(|c| c.sled_tokens)
.unwrap_or_default();
let config = GatewayConfig {
projects: projects.clone(),
sled_tokens,
};
if let Ok(data) = toml::to_string_pretty(&config) {
let _ = tokio::fs::write(&path, data).await;
@@ -518,27 +528,20 @@ pub fn spawn_gateway_bot(
gateway_project_urls: BTreeMap<String, String>,
port: u16,
gateway_event_tx: Option<tokio::sync::broadcast::Sender<super::GatewayStatusEvent>>,
perm_rx: std::sync::Arc<
tokio::sync::Mutex<
tokio::sync::mpsc::UnboundedReceiver<crate::http::context::PermissionForward>,
>,
>,
) -> (
Option<tokio::task::AbortHandle>,
tokio::sync::watch::Sender<Option<crate::rebuild::ShutdownReason>>,
) {
use crate::agents::AgentPool;
use crate::services::Services;
use tokio::sync::{broadcast, mpsc};
let (watcher_tx, _) = broadcast::channel(16);
let (perm_tx, perm_rx) = mpsc::unbounded_channel();
// Keep the sender alive for the gateway's lifetime so the matrix bot's
// `permission_listener` task doesn't exit immediately with
// "perm_rx channel closed". Previously `_perm_tx` was dropped when
// `spawn_gateway_bot` returned, closing the channel before the
// listener could even register. Story 898 (sled→gateway WS uplink)
// will eventually wire in a real sender; for now the leak keeps the
// channel open with no senders writing to it, matching the original
// intent of "listener watches forever, waiting for requests".
std::mem::forget(perm_tx);
let perm_rx = std::sync::Arc::new(tokio::sync::Mutex::new(perm_rx));
use tokio::sync::broadcast;
let (watcher_tx, _) = broadcast::channel::<crate::io::watcher::WatcherEvent>(16);
let (shutdown_tx, shutdown_rx) =
tokio::sync::watch::channel::<Option<crate::rebuild::ShutdownReason>>(None);
// shutdown_tx is intentionally NOT forgotten — the caller holds it and
@@ -611,6 +614,9 @@ mod tests {
let active = std::sync::Arc::new(tokio::sync::RwLock::new("proj".to_string()));
let (event_tx, _) = tokio::sync::broadcast::channel(4);
let (_perm_tx, perm_rx) =
tokio::sync::mpsc::unbounded_channel::<crate::http::context::PermissionForward>();
let perm_rx = std::sync::Arc::new(tokio::sync::Mutex::new(perm_rx));
let (handle, shutdown_tx) = spawn_gateway_bot(
tmp.path(),
active,
@@ -618,6 +624,7 @@ mod tests {
std::collections::BTreeMap::new(),
3001,
Some(event_tx),
perm_rx,
);
// No bot.toml in tmp → no abort handle spawned.