huskies: merge 1212 story Sled chat messages show "workspace" instead of the real project name
This commit is contained in:
@@ -21,10 +21,10 @@ use super::super::format::{
|
||||
format_disk_recovery_notification, format_disk_warning_notification, format_error_notification,
|
||||
format_merge_auto_retry_notification, format_new_items_notification,
|
||||
format_oauth_account_swapped, format_oauth_accounts_exhausted, format_rate_limit_notification,
|
||||
project_display_name, truncate_gate_output,
|
||||
truncate_gate_output,
|
||||
};
|
||||
use super::super::route::rooms_for_notification;
|
||||
use super::{find_story_name_any_stage, read_story_name};
|
||||
use super::{find_story_name_any_stage, read_story_name, resolve_project_display_name};
|
||||
|
||||
/// Format and send any pending new-item-creation notifications as a single
|
||||
/// combined message, then clear the pending buffer.
|
||||
@@ -70,8 +70,7 @@ pub fn spawn_notification_listener(
|
||||
let mut rx = watcher_rx;
|
||||
// Load initial config; re-loaded on ConfigChanged events.
|
||||
let mut config = ProjectConfig::load(&project_root).unwrap_or_default();
|
||||
let mut project_name =
|
||||
project_display_name(config.gateway_project.as_deref(), &project_root);
|
||||
let mut project_name = resolve_project_display_name(&config, &project_root);
|
||||
// Tracks when a rate-limit notification was last sent for each
|
||||
// "story_id:agent_name" key, to debounce repeated warnings.
|
||||
let mut rate_limit_last_notified: HashMap<String, Instant> = HashMap::new();
|
||||
@@ -465,8 +464,7 @@ pub fn spawn_notification_listener(
|
||||
EventAction::ReloadConfig => {
|
||||
if let Ok(new_cfg) = ProjectConfig::load(&project_root) {
|
||||
config = new_cfg;
|
||||
project_name =
|
||||
project_display_name(config.gateway_project.as_deref(), &project_root);
|
||||
project_name = resolve_project_display_name(&config, &project_root);
|
||||
}
|
||||
}
|
||||
EventAction::Skip => {}
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use crate::config::ProjectConfig;
|
||||
|
||||
use super::format::project_display_name;
|
||||
|
||||
mod listener;
|
||||
pub use listener::spawn_notification_listener;
|
||||
|
||||
@@ -39,3 +43,111 @@ pub fn read_story_name(_project_root: &Path, _stage: &str, item_id: &str) -> Str
|
||||
fn find_story_name_any_stage(project_root: &Path, item_id: &str) -> String {
|
||||
read_story_name(project_root, "", item_id)
|
||||
}
|
||||
|
||||
/// Resolve the human-readable project display name using the full precedence
|
||||
/// chain (story 1212): config `gateway_project` → `HUSKIES_GATEWAY_PROJECT`
|
||||
/// env → `HUSKIES_PROJECT_NAME` env → project-root directory basename →
|
||||
/// literal `"project"`.
|
||||
///
|
||||
/// Shared by the notification listener (initial load and `ReloadConfig`) and
|
||||
/// the gateway relay startup path so precedence cannot drift between them —
|
||||
/// the env-var overrides matter because sled containers commonly mount the
|
||||
/// project at a generic path (e.g. `/workspace`), so the directory-basename
|
||||
/// fallback alone produces a useless display name.
|
||||
pub fn resolve_project_display_name(config: &ProjectConfig, project_root: &Path) -> String {
|
||||
let gateway_project = config
|
||||
.gateway_project
|
||||
.clone()
|
||||
.or_else(|| std::env::var("HUSKIES_GATEWAY_PROJECT").ok())
|
||||
.or_else(|| std::env::var("HUSKIES_PROJECT_NAME").ok());
|
||||
project_display_name(gateway_project.as_deref(), project_root)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod resolve_project_display_name_tests {
|
||||
use super::*;
|
||||
|
||||
/// `HUSKIES_GATEWAY_PROJECT` and `HUSKIES_PROJECT_NAME` are read by
|
||||
/// `resolve_project_display_name` and are process-global, so tests that
|
||||
/// touch them must not run concurrently with each other.
|
||||
fn env_test_lock() -> std::sync::MutexGuard<'static, ()> {
|
||||
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
LOCK.lock().unwrap_or_else(|p| p.into_inner())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn falls_back_to_huskies_project_name_when_config_and_gateway_env_absent() {
|
||||
let _guard = env_test_lock();
|
||||
// SAFETY: serialised by `env_test_lock` — no other test in this
|
||||
// process reads/writes these two vars concurrently.
|
||||
unsafe {
|
||||
std::env::remove_var("HUSKIES_GATEWAY_PROJECT");
|
||||
std::env::set_var("HUSKIES_PROJECT_NAME", "my-sled-project");
|
||||
}
|
||||
|
||||
let config = ProjectConfig::default();
|
||||
let root = Path::new("/workspace");
|
||||
let result = resolve_project_display_name(&config, root);
|
||||
|
||||
unsafe {
|
||||
std::env::remove_var("HUSKIES_PROJECT_NAME");
|
||||
}
|
||||
|
||||
assert_eq!(result, "my-sled-project");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_gateway_project_wins_over_all_env_vars() {
|
||||
let _guard = env_test_lock();
|
||||
unsafe {
|
||||
std::env::set_var("HUSKIES_GATEWAY_PROJECT", "env-gateway-project");
|
||||
std::env::set_var("HUSKIES_PROJECT_NAME", "env-project-name");
|
||||
}
|
||||
|
||||
let config = ProjectConfig {
|
||||
gateway_project: Some("configured-name".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let result = resolve_project_display_name(&config, Path::new("/workspace"));
|
||||
|
||||
unsafe {
|
||||
std::env::remove_var("HUSKIES_GATEWAY_PROJECT");
|
||||
std::env::remove_var("HUSKIES_PROJECT_NAME");
|
||||
}
|
||||
|
||||
assert_eq!(result, "configured-name");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn huskies_gateway_project_env_wins_over_huskies_project_name_env() {
|
||||
let _guard = env_test_lock();
|
||||
unsafe {
|
||||
std::env::set_var("HUSKIES_GATEWAY_PROJECT", "env-gateway-project");
|
||||
std::env::set_var("HUSKIES_PROJECT_NAME", "env-project-name");
|
||||
}
|
||||
|
||||
let config = ProjectConfig::default();
|
||||
let result = resolve_project_display_name(&config, Path::new("/workspace"));
|
||||
|
||||
unsafe {
|
||||
std::env::remove_var("HUSKIES_GATEWAY_PROJECT");
|
||||
std::env::remove_var("HUSKIES_PROJECT_NAME");
|
||||
}
|
||||
|
||||
assert_eq!(result, "env-gateway-project");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn falls_back_to_directory_basename_when_nothing_configured() {
|
||||
let _guard = env_test_lock();
|
||||
unsafe {
|
||||
std::env::remove_var("HUSKIES_GATEWAY_PROJECT");
|
||||
std::env::remove_var("HUSKIES_PROJECT_NAME");
|
||||
}
|
||||
|
||||
let config = ProjectConfig::default();
|
||||
let result = resolve_project_display_name(&config, Path::new("/tmp/my-project-dir"));
|
||||
|
||||
assert_eq!(result, "my-project-dir");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ pub use format::{
|
||||
format_blocked_notification, format_disk_recovery_notification,
|
||||
format_disk_warning_notification, format_error_notification, format_stage_notification,
|
||||
};
|
||||
pub use io::resolve_project_display_name;
|
||||
pub use io::spawn_notification_listener;
|
||||
pub use io::spawn_stage_notification_subscriber;
|
||||
|
||||
|
||||
@@ -411,16 +411,14 @@ pub(crate) fn spawn_gateway_relay(startup_root: &Option<PathBuf>, status: Arc<St
|
||||
.unwrap_or_default();
|
||||
|
||||
if !relay_gateway_url.is_empty() {
|
||||
// Same precedence chain (and same shared helper) as the notification
|
||||
// listener uses (story 1212), so the relay's project name and chat
|
||||
// notifications never drift apart.
|
||||
let relay_project_name = startup_root
|
||||
.as_ref()
|
||||
.and_then(|r| config::ProjectConfig::load(r).ok())
|
||||
.and_then(|c| c.gateway_project)
|
||||
.or_else(|| std::env::var("HUSKIES_GATEWAY_PROJECT").ok())
|
||||
.or_else(|| {
|
||||
startup_root
|
||||
.as_ref()
|
||||
.and_then(|r| r.file_name())
|
||||
.map(|n| n.to_string_lossy().into_owned())
|
||||
.map(|r| {
|
||||
let cfg = config::ProjectConfig::load(r).unwrap_or_default();
|
||||
service::notifications::resolve_project_display_name(&cfg, r)
|
||||
})
|
||||
.unwrap_or_else(|| "project".to_string());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user