fix: add --all to cargo fmt in script/test and autoformat codebase
cargo fmt without --all fails with "Failed to find targets" in workspace repos. This was blocking every story's gates. Also ran cargo fmt --all to fix all existing formatting issues. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,21 +1,21 @@
|
||||
//! Slack incoming message dispatch and slash command handling.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::sync::{Mutex as TokioMutex, oneshot};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::format::markdown_to_slack;
|
||||
use super::history::{SlackConversationHistory, save_slack_history};
|
||||
use super::meta::SlackTransport;
|
||||
use crate::agents::AgentPool;
|
||||
use crate::chat::ChatTransport;
|
||||
use crate::chat::transport::matrix::{ConversationEntry, ConversationRole, RoomConversation};
|
||||
use crate::chat::util::is_permission_approval;
|
||||
use crate::slog;
|
||||
use crate::chat::ChatTransport;
|
||||
use crate::http::context::{PermissionDecision, PermissionForward};
|
||||
use super::meta::SlackTransport;
|
||||
use super::history::{SlackConversationHistory, save_slack_history};
|
||||
use super::format::markdown_to_slack;
|
||||
use crate::slog;
|
||||
|
||||
// ── Slash command types ─────────────────────────────────────────────────
|
||||
|
||||
@@ -81,8 +81,7 @@ pub struct SlackWebhookContext {
|
||||
/// Permission requests from the MCP `prompt_permission` tool arrive here.
|
||||
pub perm_rx: Arc<TokioMutex<tokio::sync::mpsc::UnboundedReceiver<PermissionForward>>>,
|
||||
/// Pending permission replies keyed by channel ID.
|
||||
pub pending_perm_replies:
|
||||
Arc<TokioMutex<HashMap<String, oneshot::Sender<PermissionDecision>>>>,
|
||||
pub pending_perm_replies: Arc<TokioMutex<HashMap<String, oneshot::Sender<PermissionDecision>>>>,
|
||||
/// Seconds before an unanswered permission prompt is auto-denied.
|
||||
pub permission_timeout_secs: u64,
|
||||
}
|
||||
@@ -154,8 +153,11 @@ pub(super) async fn handle_incoming_message(
|
||||
}
|
||||
HtopCommand::Start { duration_secs } => {
|
||||
// On Slack, htop uses native message editing for live updates.
|
||||
let snapshot =
|
||||
crate::chat::transport::matrix::htop::build_htop_message(&ctx.agents, 0, duration_secs);
|
||||
let snapshot = crate::chat::transport::matrix::htop::build_htop_message(
|
||||
&ctx.agents,
|
||||
0,
|
||||
duration_secs,
|
||||
);
|
||||
let snapshot = markdown_to_slack(&snapshot);
|
||||
let msg_id = match ctx.transport.send_message(channel, &snapshot, "").await {
|
||||
Ok(id) => id,
|
||||
@@ -179,9 +181,7 @@ pub(super) async fn handle_incoming_message(
|
||||
duration_secs,
|
||||
);
|
||||
let updated = markdown_to_slack(&updated);
|
||||
if let Err(e) =
|
||||
transport.edit_message(&ch, &msg_id, &updated, "").await
|
||||
{
|
||||
if let Err(e) = transport.edit_message(&ch, &msg_id, &updated, "").await {
|
||||
slog!("[slack] Failed to edit htop message: {e}");
|
||||
break;
|
||||
}
|
||||
@@ -245,7 +245,9 @@ pub(super) async fn handle_incoming_message(
|
||||
) {
|
||||
let response = match rmtree_cmd {
|
||||
crate::chat::transport::matrix::rmtree::RmtreeCommand::Rmtree { story_number } => {
|
||||
slog!("[slack] Handling rmtree command from {user} in {channel}: story {story_number}");
|
||||
slog!(
|
||||
"[slack] Handling rmtree command from {user} in {channel}: story {story_number}"
|
||||
);
|
||||
crate::chat::transport::matrix::rmtree::handle_rmtree(
|
||||
&ctx.bot_name,
|
||||
&story_number,
|
||||
@@ -273,7 +275,9 @@ pub(super) async fn handle_incoming_message(
|
||||
slog!("[slack] Handling reset command from {user} in {channel}");
|
||||
{
|
||||
let mut guard = ctx.history.lock().await;
|
||||
let conv = guard.entry(channel.to_string()).or_insert_with(RoomConversation::default);
|
||||
let conv = guard
|
||||
.entry(channel.to_string())
|
||||
.or_insert_with(RoomConversation::default);
|
||||
conv.session_id = None;
|
||||
conv.entries.clear();
|
||||
save_slack_history(&ctx.project_root, &guard);
|
||||
@@ -295,7 +299,9 @@ pub(super) async fn handle_incoming_message(
|
||||
story_number,
|
||||
agent_hint,
|
||||
} => {
|
||||
slog!("[slack] Handling start command from {user} in {channel}: story {story_number}");
|
||||
slog!(
|
||||
"[slack] Handling start command from {user} in {channel}: story {story_number}"
|
||||
);
|
||||
crate::chat::transport::matrix::start::handle_start(
|
||||
&ctx.bot_name,
|
||||
&story_number,
|
||||
@@ -320,8 +326,13 @@ pub(super) async fn handle_incoming_message(
|
||||
&ctx.bot_user_id,
|
||||
) {
|
||||
let response = match assign_cmd {
|
||||
crate::chat::transport::matrix::assign::AssignCommand::Assign { story_number, model } => {
|
||||
slog!("[slack] Handling assign command from {user} in {channel}: story {story_number} model {model}");
|
||||
crate::chat::transport::matrix::assign::AssignCommand::Assign {
|
||||
story_number,
|
||||
model,
|
||||
} => {
|
||||
slog!(
|
||||
"[slack] Handling assign command from {user} in {channel}: story {story_number} model {model}"
|
||||
);
|
||||
crate::chat::transport::matrix::assign::handle_assign(
|
||||
&ctx.bot_name,
|
||||
&story_number,
|
||||
@@ -352,17 +363,15 @@ async fn handle_llm_message(
|
||||
user: &str,
|
||||
user_message: &str,
|
||||
) {
|
||||
use crate::llm::providers::claude_code::{ClaudeCodeProvider, ClaudeCodeResult};
|
||||
use crate::chat::util::drain_complete_paragraphs;
|
||||
use crate::llm::providers::claude_code::{ClaudeCodeProvider, ClaudeCodeResult};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use tokio::sync::watch;
|
||||
|
||||
// Look up existing session ID for this channel.
|
||||
let resume_session_id: Option<String> = {
|
||||
let guard = ctx.history.lock().await;
|
||||
guard
|
||||
.get(channel)
|
||||
.and_then(|conv| conv.session_id.clone())
|
||||
guard.get(channel).and_then(|conv| conv.session_id.clone())
|
||||
};
|
||||
|
||||
let bot_name = &ctx.bot_name;
|
||||
@@ -383,7 +392,9 @@ async fn handle_llm_message(
|
||||
let post_task = tokio::spawn(async move {
|
||||
while let Some(chunk) = msg_rx.recv().await {
|
||||
let formatted = markdown_to_slack(&chunk);
|
||||
let _ = post_transport.send_message(&post_channel, &formatted, "").await;
|
||||
let _ = post_transport
|
||||
.send_message(&post_channel, &formatted, "")
|
||||
.await;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -472,9 +483,7 @@ async fn handle_llm_message(
|
||||
let last_text = messages
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|m| {
|
||||
m.role == crate::llm::types::Role::Assistant && !m.content.is_empty()
|
||||
})
|
||||
.find(|m| m.role == crate::llm::types::Role::Assistant && !m.content.is_empty())
|
||||
.map(|m| m.content.clone())
|
||||
.unwrap_or_default();
|
||||
if !last_text.is_empty() {
|
||||
@@ -559,7 +568,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn slash_command_maps_status() {
|
||||
assert_eq!(slash_command_to_bot_keyword("/huskies-status"), Some("status"));
|
||||
assert_eq!(
|
||||
slash_command_to_bot_keyword("/huskies-status"),
|
||||
Some("status")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -600,9 +612,8 @@ mod tests {
|
||||
response_type: "ephemeral",
|
||||
text: "hello".to_string(),
|
||||
};
|
||||
let json: serde_json::Value = serde_json::from_str(
|
||||
&serde_json::to_string(&resp).unwrap()
|
||||
).unwrap();
|
||||
let json: serde_json::Value =
|
||||
serde_json::from_str(&serde_json::to_string(&resp).unwrap()).unwrap();
|
||||
assert_eq!(json["response_type"], "ephemeral");
|
||||
assert_eq!(json["text"], "hello");
|
||||
}
|
||||
@@ -642,7 +653,10 @@ mod tests {
|
||||
};
|
||||
|
||||
let result = try_handle_command(&dispatch, &synthetic);
|
||||
assert!(result.is_some(), "status slash command should produce output via registry");
|
||||
assert!(
|
||||
result.is_some(),
|
||||
"status slash command should produce output via registry"
|
||||
);
|
||||
assert!(result.unwrap().contains("Pipeline Status"));
|
||||
}
|
||||
|
||||
@@ -671,7 +685,10 @@ mod tests {
|
||||
let result = try_handle_command(&dispatch, &synthetic);
|
||||
assert!(result.is_some(), "show slash command should produce output");
|
||||
let output = result.unwrap();
|
||||
assert!(output.contains("999"), "show output should reference the story number: {output}");
|
||||
assert!(
|
||||
output.contains("999"),
|
||||
"show output should reference the story number: {output}"
|
||||
);
|
||||
}
|
||||
|
||||
// ── rebuild command extraction ─────────────────────────────────────
|
||||
@@ -704,7 +721,10 @@ mod tests {
|
||||
"Huskies",
|
||||
"slack-bot",
|
||||
);
|
||||
assert!(result.is_none(), "'status' should not be recognised as rebuild");
|
||||
assert!(
|
||||
result.is_none(),
|
||||
"'status' should not be recognised as rebuild"
|
||||
);
|
||||
}
|
||||
|
||||
// ── reset command extraction ───────────────────────────────────────
|
||||
@@ -731,21 +751,26 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn reset_command_clears_slack_session() {
|
||||
use crate::chat::transport::matrix::{
|
||||
ConversationEntry, ConversationRole, RoomConversation,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex as TokioMutex;
|
||||
use crate::chat::transport::matrix::{ConversationEntry, ConversationRole, RoomConversation};
|
||||
|
||||
let channel = "C01ABCDEF";
|
||||
let history: SlackConversationHistory = Arc::new(TokioMutex::new({
|
||||
let mut m = HashMap::new();
|
||||
m.insert(channel.to_string(), RoomConversation {
|
||||
session_id: Some("old-session".to_string()),
|
||||
entries: vec![ConversationEntry {
|
||||
role: ConversationRole::User,
|
||||
sender: "U01GHIJKL".to_string(),
|
||||
content: "previous message".to_string(),
|
||||
}],
|
||||
});
|
||||
m.insert(
|
||||
channel.to_string(),
|
||||
RoomConversation {
|
||||
session_id: Some("old-session".to_string()),
|
||||
entries: vec![ConversationEntry {
|
||||
role: ConversationRole::User,
|
||||
sender: "U01GHIJKL".to_string(),
|
||||
content: "previous message".to_string(),
|
||||
}],
|
||||
},
|
||||
);
|
||||
m
|
||||
}));
|
||||
|
||||
@@ -755,7 +780,9 @@ mod tests {
|
||||
|
||||
{
|
||||
let mut guard = history.lock().await;
|
||||
let conv = guard.entry(channel.to_string()).or_insert_with(RoomConversation::default);
|
||||
let conv = guard
|
||||
.entry(channel.to_string())
|
||||
.or_insert_with(RoomConversation::default);
|
||||
conv.session_id = None;
|
||||
conv.entries.clear();
|
||||
save_slack_history(tmp.path(), &guard);
|
||||
@@ -862,6 +889,9 @@ mod tests {
|
||||
"Timmy",
|
||||
"@timmy:home.local",
|
||||
);
|
||||
assert!(result.is_none(), "'status' should not be recognised as assign on Slack");
|
||||
assert!(
|
||||
result.is_none(),
|
||||
"'status' should not be recognised as assign on Slack"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user