huskies: merge 1209 story Gateway lifecycle & telemetry MCP: gateway_info, restart_gateway, gateway_logs, start_story, chat_telemetry

This commit is contained in:
Huskies Agent
2026-07-18 02:21:05 +00:00
parent 3b10b29ef5
commit 8a7bff71aa
14 changed files with 584 additions and 4 deletions
+3
View File
@@ -79,6 +79,7 @@ pub fn read_bot_config_raw(config_dir: &Path) -> BotConfigFields {
password: s("password"),
slack_bot_token: s("slack_bot_token"),
slack_signing_secret: s("slack_signing_secret"),
model: s("model"),
}
}
@@ -91,6 +92,8 @@ pub struct BotConfigFields {
pub password: Option<String>,
pub slack_bot_token: Option<String>,
pub slack_signing_secret: Option<String>,
/// Claude Code model override configured for this bot (`gateway_info` MCP tool, story 1209).
pub model: Option<String>,
}
/// Write a `bot.toml` from the given content string.
+31
View File
@@ -38,13 +38,32 @@ use io::Client;
use std::collections::{BTreeMap, HashMap};
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicI64, Ordering};
use std::time::Instant;
use tokio::sync::Mutex as TokioMutex;
use tokio::sync::RwLock;
use tokio::sync::mpsc;
pub use crate::crdt_state::NodePresenceView;
// ── Uptime (gateway_info MCP tool, story 1209) ──────────────────────────────
/// Instant the gateway process started, set once by `GatewayState::new`.
static GATEWAY_START_TIME: OnceLock<Instant> = OnceLock::new();
/// Seconds elapsed since the gateway process started.
///
/// Lazily initialises the start time on first call so this never panics, but
/// `GatewayState::new` calls it once at startup so the timer reflects actual
/// process start rather than first `gateway_info` call in normal operation.
pub fn gateway_uptime_secs() -> u64 {
GATEWAY_START_TIME
.get_or_init(Instant::now)
.elapsed()
.as_secs()
}
// ── Status event broadcaster ────────────────────────────────────────────────
/// Capacity of the gateway status event broadcast channel.
@@ -272,6 +291,7 @@ impl GatewayState {
config_dir: PathBuf,
port: u16,
) -> Result<Self, String> {
GATEWAY_START_TIME.get_or_init(Instant::now);
let first_from_config = config::validate_config(&gateway_config)?;
// Restore active project from CRDT if the stored value is still valid.
let first = crate::crdt_state::read_gateway_active_project()
@@ -838,6 +858,17 @@ mod tests {
}
}
#[test]
fn gateway_uptime_secs_is_zero_or_positive_immediately_after_start() {
// Just ensure it doesn't panic and returns a sane (small) value —
// the static is process-global so we can't assert it's exactly 0.
let uptime = gateway_uptime_secs();
assert!(
uptime < 3600,
"uptime should be small in a fresh test run, got {uptime}"
);
}
#[test]
fn gateway_state_rejects_empty_config() {
let config = GatewayConfig {