cersei_tools/
remote_trigger.rs1use super::*;
4use serde::Deserialize;
5
6static TRIGGER_REGISTRY: once_cell::sync::Lazy<dashmap::DashMap<String, Vec<TriggerEvent>>> =
8 once_cell::sync::Lazy::new(dashmap::DashMap::new);
9
10#[derive(Debug, Clone, serde::Serialize)]
11pub struct TriggerEvent {
12 pub source_session: String,
13 pub target_session: String,
14 pub event_type: String,
15 pub payload: serde_json::Value,
16 pub timestamp: String,
17}
18
19pub fn drain_triggers(session_id: &str) -> Vec<TriggerEvent> {
21 TRIGGER_REGISTRY
22 .remove(session_id)
23 .map(|(_, v)| v)
24 .unwrap_or_default()
25}
26
27pub struct RemoteTriggerTool;
28
29#[async_trait]
30impl Tool for RemoteTriggerTool {
31 fn name(&self) -> &str {
32 "RemoteTrigger"
33 }
34 fn description(&self) -> &str {
35 "Send an event to another session or agent."
36 }
37 fn permission_level(&self) -> PermissionLevel {
38 PermissionLevel::Execute
39 }
40 fn category(&self) -> ToolCategory {
41 ToolCategory::Orchestration
42 }
43
44 fn input_schema(&self) -> Value {
45 serde_json::json!({
46 "type": "object",
47 "properties": {
48 "target_session": { "type": "string", "description": "Target session ID" },
49 "event_type": { "type": "string", "description": "Event type identifier" },
50 "payload": { "description": "Event payload (any JSON)" }
51 },
52 "required": ["target_session", "event_type"]
53 })
54 }
55
56 async fn execute(&self, input: Value, ctx: &ToolContext) -> ToolResult {
57 #[derive(Deserialize)]
58 struct Input {
59 target_session: String,
60 event_type: String,
61 payload: Option<Value>,
62 }
63
64 let input: Input = match serde_json::from_value(input) {
65 Ok(i) => i,
66 Err(e) => return ToolResult::error(format!("Invalid input: {}", e)),
67 };
68
69 let event = TriggerEvent {
70 source_session: ctx.session_id.clone(),
71 target_session: input.target_session.clone(),
72 event_type: input.event_type.clone(),
73 payload: input.payload.unwrap_or(Value::Null),
74 timestamp: chrono::Utc::now().to_rfc3339(),
75 };
76
77 TRIGGER_REGISTRY
78 .entry(input.target_session.clone())
79 .or_default()
80 .push(event);
81
82 ToolResult::success(format!(
83 "Trigger '{}' sent to session '{}'",
84 input.event_type, input.target_session
85 ))
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use crate::permissions::AllowAll;
93 use std::sync::Arc;
94
95 fn test_ctx() -> ToolContext {
96 ToolContext {
97 working_dir: std::env::temp_dir(),
98 session_id: "sender".into(),
99 permissions: Arc::new(AllowAll),
100 cost_tracker: Arc::new(CostTracker::new()),
101 mcp_manager: None,
102 extensions: Extensions::default(),
103 }
104 }
105
106 #[tokio::test]
107 async fn test_trigger_send_receive() {
108 let tool = RemoteTriggerTool;
109 let result = tool
110 .execute(
111 serde_json::json!({
112 "target_session": "receiver",
113 "event_type": "tests_complete",
114 "payload": {"passed": 42}
115 }),
116 &test_ctx(),
117 )
118 .await;
119
120 assert!(!result.is_error);
121 assert!(result.content.contains("sent"));
122
123 let events = drain_triggers("receiver");
124 assert_eq!(events.len(), 1);
125 assert_eq!(events[0].event_type, "tests_complete");
126 assert_eq!(events[0].source_session, "sender");
127 }
128}