huskies: merge 1106 story Chat bootstrap Phase 1: new project chat command spawns a bare project container and registers it with the gateway

This commit is contained in:
dave
2026-05-16 22:39:20 +00:00
parent 5c63618b30
commit 10d992a7e4
11 changed files with 385 additions and 1 deletions
@@ -1,5 +1,6 @@
//! Matrix bot context — shared state for the Matrix bot (rooms, history, permissions).
use crate::chat::ChatTransport;
use crate::service::gateway::config::ProjectEntry;
use crate::service::timer::TimerStore;
use crate::services::Services;
use matrix_sdk::ruma::{OwnedEventId, OwnedRoomId, OwnedUserId};
@@ -94,6 +95,11 @@ pub struct BotContext {
/// Used to proxy bot commands to the active project over WebSocket (`/ws`).
/// Empty in standalone mode.
pub gateway_project_urls: BTreeMap<String, String>,
/// In gateway mode: shared live projects map from [`GatewayState`].
///
/// The `new project` command writes here so HTTP handlers see the new entry
/// immediately without requiring a gateway restart. `None` in standalone mode.
pub gateway_projects_store: Option<Arc<RwLock<BTreeMap<String, ProjectEntry>>>>,
/// Pipeline transition events buffered since the last LLM turn.
///
/// A background task appends one compact audit line per real stage
@@ -300,6 +306,7 @@ mod tests {
gateway_active_project,
gateway_projects,
gateway_project_urls,
gateway_projects_store: None,
pending_pipeline_events: Arc::new(TokioMutex::new(Vec::new())),
pending_gateway_events: Arc::new(TokioMutex::new(Vec::new())),
handled_incoming_event_ids: Arc::new(TokioMutex::new(SeenEventIds::new(
@@ -193,7 +193,7 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
if ctx.is_gateway() {
// Commands that are meaningful on the gateway itself (no project state needed).
const GATEWAY_LOCAL_COMMANDS: &[&str] =
&["help", "ambient", "reset", "switch", "all_status"];
&["help", "ambient", "reset", "switch", "all_status", "new"];
let stripped = crate::chat::util::strip_bot_mention(
&user_message,
@@ -260,6 +260,38 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
// Gateway-local commands and freeform text fall through to normal handling below.
}
// In gateway mode, handle the "new project <name>" command to bootstrap a
// bare project container and register it with the gateway.
if ctx.is_gateway()
&& let Some(project_name) = super::super::super::new_project::extract_new_project_command(
&user_message,
&ctx.services.bot_name,
ctx.matrix_user_id.as_str(),
)
{
slog!("[matrix-bot] Handling new project command from {sender}: name={project_name:?}");
let response = if let Some(ref store) = ctx.gateway_projects_store {
super::super::super::new_project::handle_new_project(
&project_name,
store,
&ctx.services.project_root,
)
.await
} else {
"Gateway projects store unavailable — cannot create project.".to_string()
};
let html = markdown_to_html(&response);
if let Ok(msg_id) = ctx
.transport
.send_message(&room_id_str, &response, &html)
.await
&& let Ok(event_id) = msg_id.parse()
{
ctx.bot_sent_event_ids.lock().await.insert(event_id);
}
return;
}
// Check for bot-level commands (help, status, ambient, …) before invoking
// the LLM. All commands are registered in commands.rs — no special-casing
// needed here.
@@ -30,6 +30,13 @@ pub async fn run_bot(
gateway_active_project: Option<Arc<RwLock<String>>>,
gateway_projects: Vec<String>,
gateway_project_urls: std::collections::BTreeMap<String, String>,
gateway_projects_store: Option<
Arc<
RwLock<
std::collections::BTreeMap<String, crate::service::gateway::config::ProjectEntry>,
>,
>,
>,
timer_store: Arc<TimerStore>,
gateway_event_rx: Option<
tokio::sync::broadcast::Receiver<crate::service::gateway::GatewayStatusEvent>,
@@ -399,6 +406,7 @@ pub async fn run_bot(
gateway_active_project,
gateway_projects,
gateway_project_urls,
gateway_projects_store,
pending_pipeline_events,
pending_gateway_events,
handled_incoming_event_ids: Arc::new(TokioMutex::new(super::context::SeenEventIds::new(