1use crate::kernel::ExecutionId;
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(tag = "type", rename_all = "snake_case")]
12pub enum InboxMessage {
13 Control(ControlMessage),
15 Guidance(GuidanceMessage),
17 Evidence(EvidenceUpdate),
19 A2a(A2aMessage),
21}
22
23impl InboxMessage {
24 pub fn message_type(&self) -> InboxMessageType {
26 match self {
27 InboxMessage::Control(_) => InboxMessageType::Control,
28 InboxMessage::Guidance(_) => InboxMessageType::Guidance,
29 InboxMessage::Evidence(_) => InboxMessageType::Evidence,
30 InboxMessage::A2a(_) => InboxMessageType::A2a,
31 }
32 }
33
34 pub fn id(&self) -> &str {
36 match self {
37 InboxMessage::Control(m) => &m.id,
38 InboxMessage::Guidance(m) => &m.id,
39 InboxMessage::Evidence(m) => &m.id,
40 InboxMessage::A2a(m) => &m.id,
41 }
42 }
43
44 pub fn execution_id(&self) -> &ExecutionId {
46 match self {
47 InboxMessage::Control(m) => &m.execution_id,
48 InboxMessage::Guidance(m) => &m.execution_id,
49 InboxMessage::Evidence(m) => &m.execution_id,
50 InboxMessage::A2a(m) => &m.execution_id,
51 }
52 }
53
54 pub fn priority_order(&self) -> u8 {
58 match self {
59 InboxMessage::Control(_) => 0, InboxMessage::Evidence(e) if e.impact == EvidenceImpact::ContradictsPlan => 1,
61 InboxMessage::Evidence(_) => 2,
62 InboxMessage::Guidance(g) if g.priority == GuidancePriority::High => 3,
63 InboxMessage::Guidance(_) => 4,
64 InboxMessage::A2a(_) => 5, }
66 }
67
68 pub fn is_control(&self) -> bool {
70 matches!(self, InboxMessage::Control(_))
71 }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(rename_all = "snake_case")]
77pub enum InboxMessageType {
78 Control,
79 Guidance,
80 Evidence,
81 A2a,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct ControlMessage {
91 pub id: String,
93 pub execution_id: ExecutionId,
95 pub action: ControlAction,
97 pub reason: Option<String>,
99 pub actor: String,
101 pub created_at: DateTime<Utc>,
103}
104
105impl ControlMessage {
106 pub fn new(execution_id: ExecutionId, action: ControlAction, actor: impl Into<String>) -> Self {
108 Self {
109 id: uuid::Uuid::new_v4().to_string(),
110 execution_id,
111 action,
112 reason: None,
113 actor: actor.into(),
114 created_at: Utc::now(),
115 }
116 }
117
118 pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
120 self.reason = Some(reason.into());
121 self
122 }
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
127#[serde(rename_all = "snake_case")]
128pub enum ControlAction {
129 Pause,
131 Resume,
133 Cancel,
135 Checkpoint,
137 Compact,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct GuidanceMessage {
148 pub id: String,
150 pub execution_id: ExecutionId,
152 pub from: GuidanceSource,
154 pub content: String,
156 pub context: Option<serde_json::Value>,
158 pub priority: GuidancePriority,
160 pub created_at: DateTime<Utc>,
162}
163
164impl GuidanceMessage {
165 pub fn from_user(execution_id: ExecutionId, content: impl Into<String>) -> Self {
167 Self {
168 id: uuid::Uuid::new_v4().to_string(),
169 execution_id,
170 from: GuidanceSource::User,
171 content: content.into(),
172 context: None,
173 priority: GuidancePriority::Medium,
174 created_at: Utc::now(),
175 }
176 }
177
178 pub fn with_priority(mut self, priority: GuidancePriority) -> Self {
180 self.priority = priority;
181 self
182 }
183
184 pub fn with_context(mut self, context: serde_json::Value) -> Self {
186 self.context = Some(context);
187 self
188 }
189}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
193#[serde(rename_all = "snake_case")]
194pub enum GuidanceSource {
195 User,
196 System,
197 Agent,
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
202#[serde(rename_all = "snake_case")]
203pub enum GuidancePriority {
204 Low,
205 Medium,
206 High,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct EvidenceUpdate {
216 pub id: String,
218 pub execution_id: ExecutionId,
220 pub source: EvidenceSource,
222 pub title: String,
224 pub content: serde_json::Value,
226 pub confidence: Option<f64>,
228 pub impact: EvidenceImpact,
230 pub created_at: DateTime<Utc>,
232}
233
234impl EvidenceUpdate {
235 pub fn new(
237 execution_id: ExecutionId,
238 source: EvidenceSource,
239 title: impl Into<String>,
240 content: serde_json::Value,
241 impact: EvidenceImpact,
242 ) -> Self {
243 Self {
244 id: uuid::Uuid::new_v4().to_string(),
245 execution_id,
246 source,
247 title: title.into(),
248 content,
249 confidence: None,
250 impact,
251 created_at: Utc::now(),
252 }
253 }
254
255 pub fn with_confidence(mut self, confidence: f64) -> Self {
257 self.confidence = Some(confidence.clamp(0.0, 1.0));
258 self
259 }
260}
261
262#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
264#[serde(rename_all = "snake_case")]
265pub enum EvidenceSource {
266 Discovery,
268 ToolResult,
270 External,
272 Memory,
274}
275
276#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
278#[serde(rename_all = "snake_case")]
279pub enum EvidenceImpact {
280 Informational,
282 RequiresReview,
284 ContradictsPlan,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct A2aMessage {
295 pub id: String,
297 pub execution_id: ExecutionId,
299 pub from_agent: String,
301 pub message_type: String,
303 pub payload: serde_json::Value,
305 pub created_at: DateTime<Utc>,
307}
308
309impl A2aMessage {
310 pub fn new(
312 execution_id: ExecutionId,
313 from_agent: impl Into<String>,
314 message_type: impl Into<String>,
315 payload: serde_json::Value,
316 ) -> Self {
317 Self {
318 id: uuid::Uuid::new_v4().to_string(),
319 execution_id,
320 from_agent: from_agent.into(),
321 message_type: message_type.into(),
322 payload,
323 created_at: Utc::now(),
324 }
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 #[test]
333 fn test_control_message_priority() {
334 let exec_id = ExecutionId::new();
335 let control = InboxMessage::Control(ControlMessage::new(
336 exec_id.clone(),
337 ControlAction::Pause,
338 "test_user",
339 ));
340
341 assert_eq!(control.priority_order(), 0);
342 assert!(control.is_control());
343 }
344
345 #[test]
346 fn test_evidence_priority_contradicts() {
347 let exec_id = ExecutionId::new();
348 let evidence = InboxMessage::Evidence(EvidenceUpdate::new(
349 exec_id,
350 EvidenceSource::Discovery,
351 "Found conflict",
352 serde_json::json!({}),
353 EvidenceImpact::ContradictsPlan,
354 ));
355
356 assert_eq!(evidence.priority_order(), 1);
357 }
358
359 #[test]
360 fn test_guidance_priority() {
361 let exec_id = ExecutionId::new();
362 let high = InboxMessage::Guidance(
363 GuidanceMessage::from_user(exec_id.clone(), "Focus on EU")
364 .with_priority(GuidancePriority::High),
365 );
366 let low = InboxMessage::Guidance(
367 GuidanceMessage::from_user(exec_id, "Also check this")
368 .with_priority(GuidancePriority::Low),
369 );
370
371 assert_eq!(high.priority_order(), 3);
372 assert_eq!(low.priority_order(), 4);
373 }
374
375 #[test]
376 fn test_message_sorting() {
377 let exec_id = ExecutionId::new();
378 let mut messages = [
379 InboxMessage::Guidance(GuidanceMessage::from_user(exec_id.clone(), "test")),
380 InboxMessage::Control(ControlMessage::new(
381 exec_id.clone(),
382 ControlAction::Pause,
383 "user",
384 )),
385 InboxMessage::Evidence(EvidenceUpdate::new(
386 exec_id,
387 EvidenceSource::Discovery,
388 "Found",
389 serde_json::json!({}),
390 EvidenceImpact::Informational,
391 )),
392 ];
393
394 messages.sort_by_key(|m| m.priority_order());
396
397 assert!(messages[0].is_control());
399 assert!(matches!(messages[1], InboxMessage::Evidence(_)));
400 assert!(matches!(messages[2], InboxMessage::Guidance(_)));
401 }
402}