Skip to main content

j_agent/tools/
send_message.rs

1use crate::agent::thread_identity::current_agent_name;
2use crate::teammate::TeammateManager;
3use crate::tools::{PlanDecision, Tool, ToolResult, parse_tool_args, schema_to_tool_params};
4use schemars::JsonSchema;
5use serde::Deserialize;
6use serde_json::Value;
7use std::borrow::Cow;
8use std::sync::{Arc, Mutex, atomic::AtomicBool};
9
10/// SendMessage 参数
11#[derive(Deserialize, JsonSchema)]
12struct SendMessageParams {
13    /// Message content to send
14    message: String,
15    /// Optional: target agent name (e.g. "@Backend"). If omitted, message is broadcast to all.
16    #[serde(default)]
17    to: Option<String>,
18}
19
20/// SendMessage 工具:在聊天室中发送消息给其他 agent
21pub struct SendMessageTool {
22    pub teammate_manager: Arc<Mutex<TeammateManager>>,
23}
24
25impl SendMessageTool {
26    pub const NAME: &'static str = "SendMessage";
27}
28
29impl Tool for SendMessageTool {
30    fn name(&self) -> &str {
31        Self::NAME
32    }
33
34    fn description(&self) -> Cow<'_, str> {
35        r#"
36        Send a message to teammates. This is the ONLY way to make your words visible to other agents.
37
38        Your plain text output (prose without tool calls) is private thinking — only you and the human user can see it.
39        To communicate with teammates, you MUST use this tool.
40
41        Usage:
42        - message: The text content to send
43        - to: Optional target agent name. The message is broadcast to everyone,
44              but @mentioning wakes the target agent (interrupts their idle state).
45
46        Messages appear in the chat as: <YourName> @Target message
47        All agents receive the message, but the @mentioned agent knows it's addressed to them.
48
49        Note: If new messages arrive while you're thinking, your SendMessage may be held for
50        re-evaluation. You'll receive a system_reminder and can choose to resend or revise.
51
52        Example:
53        {"message": "API endpoints are done, check routes/api.js", "to": "Frontend"}
54        {"message": "All frontend pages are complete", "to": "Main"}
55        {"message": "Heads up: database schema has been updated"}
56        "#.into()
57    }
58
59    fn parameters_schema(&self) -> Value {
60        schema_to_tool_params::<SendMessageParams>()
61    }
62
63    fn execute(&self, arguments: &str, _cancelled: &Arc<AtomicBool>) -> ToolResult {
64        let params: SendMessageParams = match parse_tool_args(arguments) {
65            Ok(p) => p,
66            Err(e) => return e,
67        };
68
69        if params.message.trim().is_empty() {
70            return ToolResult {
71                output: "Message cannot be empty".to_string(),
72                is_error: true,
73                images: vec![],
74                plan_decision: PlanDecision::None,
75            };
76        }
77
78        let from = current_agent_name();
79        let to = params.to.as_deref().map(|s| {
80            let s = s.trim_start_matches('@');
81            // 兼容全名格式:Teammate@Frontend → Frontend, SubAgent@search → search
82            s.strip_prefix("Teammate@")
83                .or_else(|| s.strip_prefix("SubAgent@"))
84                .unwrap_or(s)
85        });
86
87        match self.teammate_manager.lock() {
88            Ok(manager) => {
89                manager.broadcast(&from, &params.message, to);
90                let target_desc = to
91                    .map(|t| format!("@{}", t))
92                    .unwrap_or_else(|| "all teammates".to_string());
93                ToolResult {
94                    output: format!("Message sent to {}", target_desc),
95                    is_error: false,
96                    images: vec![],
97                    plan_decision: PlanDecision::None,
98                }
99            }
100            Err(_) => ToolResult {
101                output: "Failed to acquire teammate manager lock".to_string(),
102                is_error: true,
103                images: vec![],
104                plan_decision: PlanDecision::None,
105            },
106        }
107    }
108
109    fn requires_confirmation(&self) -> bool {
110        false
111    }
112
113    fn is_available(&self) -> bool {
114        self.teammate_manager
115            .lock()
116            .map(|m| m.has_active_teammates())
117            .unwrap_or(false)
118    }
119}