huskies: merge 626_refactor_introduce_services_bundle_and_migrate_appcontext_matrix_transport
This commit is contained in:
@@ -52,7 +52,7 @@ pub(super) async fn on_room_message(
|
||||
}
|
||||
|
||||
// Ignore the bot's own messages to prevent echo loops.
|
||||
if ev.sender == ctx.bot_user_id {
|
||||
if ev.sender == ctx.matrix_user_id {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -74,10 +74,15 @@ pub(super) async fn on_room_message(
|
||||
// Only respond when the bot is directly addressed (mentioned by name/ID),
|
||||
// when the message is a reply to one of the bot's own messages, or when
|
||||
// ambient mode is enabled for this room.
|
||||
let is_addressed = mentions_bot(&body, formatted_body.as_deref(), &ctx.bot_user_id)
|
||||
let is_addressed = mentions_bot(&body, formatted_body.as_deref(), &ctx.matrix_user_id)
|
||||
|| is_reply_to_bot(ev.content.relates_to.as_ref(), &ctx.bot_sent_event_ids).await;
|
||||
let room_id_str = incoming_room_id.to_string();
|
||||
let is_ambient = ctx.ambient_rooms.lock().unwrap().contains(&room_id_str);
|
||||
let is_ambient = ctx
|
||||
.services
|
||||
.ambient_rooms
|
||||
.lock()
|
||||
.unwrap()
|
||||
.contains(&room_id_str);
|
||||
|
||||
// Always let "ambient on" through — it is the one command that must work
|
||||
// even when the bot is not mentioned and ambient mode is off, otherwise
|
||||
@@ -98,7 +103,7 @@ pub(super) async fn on_room_message(
|
||||
if is_ambient
|
||||
&& !is_addressed
|
||||
&& !is_ambient_on
|
||||
&& is_addressed_to_other(&body, &ctx.bot_user_id, &ctx.bot_name)
|
||||
&& is_addressed_to_other(&body, &ctx.matrix_user_id, &ctx.services.bot_name)
|
||||
{
|
||||
slog!(
|
||||
"[matrix-bot] Ignoring ambient message addressed to another bot (sender={})",
|
||||
@@ -144,8 +149,8 @@ pub(super) async fn on_room_message(
|
||||
// If there is a pending permission prompt for this room, interpret the
|
||||
// message as a yes/no response instead of starting a new chat.
|
||||
{
|
||||
let mut pending = ctx.pending_perm_replies.lock().await;
|
||||
if let Some(tx) = pending.remove(&incoming_room_id) {
|
||||
let mut pending = ctx.services.pending_perm_replies.lock().await;
|
||||
if let Some(tx) = pending.remove(incoming_room_id.as_str()) {
|
||||
let decision = if is_permission_approval(&body) {
|
||||
PermissionDecision::Approve
|
||||
} else {
|
||||
@@ -190,8 +195,8 @@ pub(super) async fn on_room_message(
|
||||
|
||||
let stripped = crate::chat::util::strip_bot_mention(
|
||||
&user_message,
|
||||
&ctx.bot_name,
|
||||
ctx.bot_user_id.as_str(),
|
||||
&ctx.services.bot_name,
|
||||
ctx.matrix_user_id.as_str(),
|
||||
)
|
||||
.trim()
|
||||
.trim_start_matches(|c: char| !c.is_alphanumeric())
|
||||
@@ -257,11 +262,11 @@ pub(super) async fn on_room_message(
|
||||
// the LLM. All commands are registered in commands.rs — no special-casing
|
||||
// needed here.
|
||||
let dispatch = super::super::commands::CommandDispatch {
|
||||
bot_name: &ctx.bot_name,
|
||||
bot_user_id: ctx.bot_user_id.as_str(),
|
||||
bot_name: &ctx.services.bot_name,
|
||||
bot_user_id: ctx.matrix_user_id.as_str(),
|
||||
project_root: &effective_root,
|
||||
agents: &ctx.agents,
|
||||
ambient_rooms: &ctx.ambient_rooms,
|
||||
agents: &ctx.services.agents,
|
||||
ambient_rooms: &ctx.services.ambient_rooms,
|
||||
room_id: &room_id_str,
|
||||
};
|
||||
if let Some((response, response_html)) =
|
||||
@@ -283,8 +288,8 @@ pub(super) async fn on_room_message(
|
||||
// start) and cannot be handled by the sync command registry.
|
||||
if let Some(assign_cmd) = super::super::assign::extract_assign_command(
|
||||
&user_message,
|
||||
&ctx.bot_name,
|
||||
ctx.bot_user_id.as_str(),
|
||||
&ctx.services.bot_name,
|
||||
ctx.matrix_user_id.as_str(),
|
||||
) {
|
||||
let response = match assign_cmd {
|
||||
super::super::assign::AssignCommand::Assign {
|
||||
@@ -295,18 +300,18 @@ pub(super) async fn on_room_message(
|
||||
"[matrix-bot] Handling assign command from {sender}: story {story_number} model={model}"
|
||||
);
|
||||
super::super::assign::handle_assign(
|
||||
&ctx.bot_name,
|
||||
&ctx.services.bot_name,
|
||||
&story_number,
|
||||
&model,
|
||||
&effective_root,
|
||||
&ctx.agents,
|
||||
&ctx.services.agents,
|
||||
)
|
||||
.await
|
||||
}
|
||||
super::super::assign::AssignCommand::BadArgs => {
|
||||
format!(
|
||||
"Usage: `{} assign <number> <model>` (e.g. `assign 42 opus`)",
|
||||
ctx.bot_name
|
||||
ctx.services.bot_name
|
||||
)
|
||||
}
|
||||
};
|
||||
@@ -326,8 +331,8 @@ pub(super) async fn on_room_message(
|
||||
// and cannot be handled by the sync command registry.
|
||||
if let Some(htop_cmd) = super::super::htop::extract_htop_command(
|
||||
&user_message,
|
||||
&ctx.bot_name,
|
||||
ctx.bot_user_id.as_str(),
|
||||
&ctx.services.bot_name,
|
||||
ctx.matrix_user_id.as_str(),
|
||||
) {
|
||||
slog!("[matrix-bot] Handling htop command from {sender}: {htop_cmd:?}");
|
||||
match htop_cmd {
|
||||
@@ -344,7 +349,7 @@ pub(super) async fn on_room_message(
|
||||
&ctx.transport,
|
||||
&room_id_str,
|
||||
&ctx.htop_sessions,
|
||||
Arc::clone(&ctx.agents),
|
||||
Arc::clone(&ctx.services.agents),
|
||||
duration_secs,
|
||||
)
|
||||
.await;
|
||||
@@ -357,22 +362,22 @@ pub(super) async fn on_room_message(
|
||||
// and cannot be handled by the sync command registry.
|
||||
if let Some(del_cmd) = super::super::delete::extract_delete_command(
|
||||
&user_message,
|
||||
&ctx.bot_name,
|
||||
ctx.bot_user_id.as_str(),
|
||||
&ctx.services.bot_name,
|
||||
ctx.matrix_user_id.as_str(),
|
||||
) {
|
||||
let response = match del_cmd {
|
||||
super::super::delete::DeleteCommand::Delete { story_number } => {
|
||||
slog!("[matrix-bot] Handling delete command from {sender}: story {story_number}");
|
||||
super::super::delete::handle_delete(
|
||||
&ctx.bot_name,
|
||||
&ctx.services.bot_name,
|
||||
&story_number,
|
||||
&effective_root,
|
||||
&ctx.agents,
|
||||
&ctx.services.agents,
|
||||
)
|
||||
.await
|
||||
}
|
||||
super::super::delete::DeleteCommand::BadArgs => {
|
||||
format!("Usage: `{} delete <number>`", ctx.bot_name)
|
||||
format!("Usage: `{} delete <number>`", ctx.services.bot_name)
|
||||
}
|
||||
};
|
||||
let html = markdown_to_html(&response);
|
||||
@@ -391,22 +396,22 @@ pub(super) async fn on_room_message(
|
||||
// and cannot be handled by the sync command registry.
|
||||
if let Some(rmtree_cmd) = super::super::rmtree::extract_rmtree_command(
|
||||
&user_message,
|
||||
&ctx.bot_name,
|
||||
ctx.bot_user_id.as_str(),
|
||||
&ctx.services.bot_name,
|
||||
ctx.matrix_user_id.as_str(),
|
||||
) {
|
||||
let response = match rmtree_cmd {
|
||||
super::super::rmtree::RmtreeCommand::Rmtree { story_number } => {
|
||||
slog!("[matrix-bot] Handling rmtree command from {sender}: story {story_number}");
|
||||
super::super::rmtree::handle_rmtree(
|
||||
&ctx.bot_name,
|
||||
&ctx.services.bot_name,
|
||||
&story_number,
|
||||
&effective_root,
|
||||
&ctx.agents,
|
||||
&ctx.services.agents,
|
||||
)
|
||||
.await
|
||||
}
|
||||
super::super::rmtree::RmtreeCommand::BadArgs => {
|
||||
format!("Usage: `{} rmtree <number>`", ctx.bot_name)
|
||||
format!("Usage: `{} rmtree <number>`", ctx.services.bot_name)
|
||||
}
|
||||
};
|
||||
let html = markdown_to_html(&response);
|
||||
@@ -425,8 +430,8 @@ pub(super) async fn on_room_message(
|
||||
// be handled by the sync command registry.
|
||||
if let Some(start_cmd) = super::super::start::extract_start_command(
|
||||
&user_message,
|
||||
&ctx.bot_name,
|
||||
ctx.bot_user_id.as_str(),
|
||||
&ctx.services.bot_name,
|
||||
ctx.matrix_user_id.as_str(),
|
||||
) {
|
||||
let response = match start_cmd {
|
||||
super::super::start::StartCommand::Start {
|
||||
@@ -437,18 +442,18 @@ pub(super) async fn on_room_message(
|
||||
"[matrix-bot] Handling start command from {sender}: story {story_number} agent={agent_hint:?}"
|
||||
);
|
||||
super::super::start::handle_start(
|
||||
&ctx.bot_name,
|
||||
&ctx.services.bot_name,
|
||||
&story_number,
|
||||
agent_hint.as_deref(),
|
||||
&effective_root,
|
||||
&ctx.agents,
|
||||
&ctx.services.agents,
|
||||
)
|
||||
.await
|
||||
}
|
||||
super::super::start::StartCommand::BadArgs => {
|
||||
format!(
|
||||
"Usage: `{} start <number>` or `{} start <number> opus`",
|
||||
ctx.bot_name, ctx.bot_name
|
||||
ctx.services.bot_name, ctx.services.bot_name
|
||||
)
|
||||
}
|
||||
};
|
||||
@@ -468,17 +473,17 @@ pub(super) async fn on_room_message(
|
||||
// conversation history and cannot be handled by the sync command registry.
|
||||
if super::super::reset::extract_reset_command(
|
||||
&user_message,
|
||||
&ctx.bot_name,
|
||||
ctx.bot_user_id.as_str(),
|
||||
&ctx.services.bot_name,
|
||||
ctx.matrix_user_id.as_str(),
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
slog!("[matrix-bot] Handling reset command from {sender}");
|
||||
let response = super::super::reset::handle_reset(
|
||||
&ctx.bot_name,
|
||||
&ctx.services.bot_name,
|
||||
&incoming_room_id,
|
||||
&ctx.history,
|
||||
&ctx.project_root,
|
||||
&ctx.services.project_root,
|
||||
)
|
||||
.await;
|
||||
let html = markdown_to_html(&response);
|
||||
@@ -497,8 +502,8 @@ pub(super) async fn on_room_message(
|
||||
// and cannot be handled by the sync command registry.
|
||||
if super::super::rebuild::extract_rebuild_command(
|
||||
&user_message,
|
||||
&ctx.bot_name,
|
||||
ctx.bot_user_id.as_str(),
|
||||
&ctx.services.bot_name,
|
||||
ctx.matrix_user_id.as_str(),
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
@@ -514,9 +519,12 @@ pub(super) async fn on_room_message(
|
||||
{
|
||||
ctx.bot_sent_event_ids.lock().await.insert(event_id);
|
||||
}
|
||||
let response =
|
||||
super::super::rebuild::handle_rebuild(&ctx.bot_name, &ctx.project_root, &ctx.agents)
|
||||
.await;
|
||||
let response = super::super::rebuild::handle_rebuild(
|
||||
&ctx.services.bot_name,
|
||||
&ctx.services.project_root,
|
||||
&ctx.services.agents,
|
||||
)
|
||||
.await;
|
||||
let html = markdown_to_html(&response);
|
||||
if let Ok(msg_id) = ctx
|
||||
.transport
|
||||
@@ -534,8 +542,8 @@ pub(super) async fn on_room_message(
|
||||
if let Some(ref active_project) = ctx.gateway_active_project {
|
||||
let stripped = crate::chat::util::strip_bot_mention(
|
||||
&user_message,
|
||||
&ctx.bot_name,
|
||||
ctx.bot_user_id.as_str(),
|
||||
&ctx.services.bot_name,
|
||||
ctx.matrix_user_id.as_str(),
|
||||
)
|
||||
.trim()
|
||||
.trim_start_matches(|c: char| !c.is_alphanumeric())
|
||||
@@ -574,14 +582,14 @@ pub(super) async fn on_room_message(
|
||||
// be handled by the sync command registry.
|
||||
if let Some(timer_cmd) = crate::service::timer::extract_timer_command(
|
||||
&user_message,
|
||||
&ctx.bot_name,
|
||||
ctx.bot_user_id.as_str(),
|
||||
&ctx.services.bot_name,
|
||||
ctx.matrix_user_id.as_str(),
|
||||
) {
|
||||
slog!("[matrix-bot] Handling timer command from {sender}: {timer_cmd:?}");
|
||||
let response = crate::service::timer::handle_timer_command(
|
||||
timer_cmd,
|
||||
&ctx.timer_store,
|
||||
&ctx.project_root,
|
||||
&ctx.services.project_root,
|
||||
)
|
||||
.await;
|
||||
let html = markdown_to_html(&response);
|
||||
@@ -620,7 +628,7 @@ pub(super) async fn handle_message(
|
||||
|
||||
// The prompt is just the current message with sender attribution.
|
||||
// Prior conversation context is carried by the Claude Code session.
|
||||
let bot_name = &ctx.bot_name;
|
||||
let bot_name = &ctx.services.bot_name;
|
||||
let active_project_ctx = if let Some(ref ap) = ctx.gateway_active_project {
|
||||
let name = ap.read().await.clone();
|
||||
format!("[Active project: {name}]\n")
|
||||
@@ -671,7 +679,7 @@ pub(super) async fn handle_message(
|
||||
// The gateway proxies tool calls to the active project automatically.
|
||||
// In standalone mode, use the project root directly.
|
||||
let project_root_str = if ctx.is_gateway() {
|
||||
ctx.project_root.to_string_lossy().to_string()
|
||||
ctx.services.project_root.to_string_lossy().to_string()
|
||||
} else {
|
||||
ctx.effective_project_root()
|
||||
.await
|
||||
@@ -701,7 +709,7 @@ pub(super) async fn handle_message(
|
||||
|
||||
// Lock the permission receiver for the duration of this chat session.
|
||||
// Permission requests from the MCP `prompt_permission` tool arrive here.
|
||||
let mut perm_rx_guard = ctx.perm_rx.lock().await;
|
||||
let mut perm_rx_guard = ctx.services.perm_rx.lock().await;
|
||||
|
||||
let result = loop {
|
||||
tokio::select! {
|
||||
@@ -726,18 +734,18 @@ pub(super) async fn handle_message(
|
||||
|
||||
// Store the MCP oneshot sender so the event handler can
|
||||
// resolve it when the user replies yes/no.
|
||||
ctx.pending_perm_replies
|
||||
ctx.services.pending_perm_replies
|
||||
.lock()
|
||||
.await
|
||||
.insert(room_id.clone(), perm_fwd.response_tx);
|
||||
.insert(room_id.to_string(), perm_fwd.response_tx);
|
||||
|
||||
// Spawn a timeout task: auto-deny if the user does not respond.
|
||||
let pending = Arc::clone(&ctx.pending_perm_replies);
|
||||
let timeout_room_id = room_id.clone();
|
||||
let pending = Arc::clone(&ctx.services.pending_perm_replies);
|
||||
let timeout_room_id = room_id.to_string();
|
||||
let timeout_transport = Arc::clone(&ctx.transport);
|
||||
let timeout_room_id_str = room_id_str.clone();
|
||||
let timeout_sent_ids = Arc::clone(&ctx.bot_sent_event_ids);
|
||||
let timeout_secs = ctx.permission_timeout_secs;
|
||||
let timeout_secs = ctx.services.permission_timeout_secs;
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_secs(timeout_secs)).await;
|
||||
if let Some(tx) = pending.lock().await.remove(&timeout_room_id) {
|
||||
@@ -844,7 +852,7 @@ pub(super) async fn handle_message(
|
||||
}
|
||||
|
||||
// Persist to disk so history survives server restarts.
|
||||
save_history(&ctx.project_root, &guard);
|
||||
save_history(&ctx.services.project_root, &guard);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user