Skip to main content

convergio_types/
events.rs

1//! Domain events — the heartbeat of the system.
2//!
3//! Every action, message, and state change is a DomainEvent.
4//! Events use human names, not IDs. "Elena", not "agent-7f3a".
5
6use chrono::{DateTime, Utc};
7
8/// Who performed the action.
9#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
10pub struct ActorName {
11    /// Human name (e.g. "Elena", "Legal Corp", "convergio-mesh").
12    pub name: String,
13    /// Organization if applicable.
14    pub org: Option<String>,
15    /// Node where the actor is running.
16    pub node: Option<String>,
17}
18
19/// Context for an event — links to plan/task/org if applicable.
20#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
21pub struct EventContext {
22    pub org_id: Option<String>,
23    pub plan_id: Option<i64>,
24    pub task_id: Option<i64>,
25}
26
27/// A domain event — streamable via SSE, persistable, subscribable.
28#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
29pub struct DomainEvent {
30    pub actor: ActorName,
31    pub kind: EventKind,
32    pub timestamp: DateTime<Utc>,
33    pub context: EventContext,
34}
35
36/// Typed event kinds covering orchestration, communication, and system events.
37#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
38#[serde(tag = "type")]
39pub enum EventKind {
40    // Orchestration
41    PlanCreated {
42        plan_id: i64,
43        name: String,
44    },
45    TaskAssigned {
46        task_id: i64,
47        agent: String,
48        org: String,
49    },
50    TaskCompleted {
51        task_id: i64,
52    },
53    PlanCompleted {
54        plan_id: i64,
55        name: String,
56    },
57    WaveCompleted {
58        wave_id: i64,
59        plan_id: i64,
60    },
61
62    // Communication (visible in real-time UI)
63    MessageSent {
64        from: String,
65        to: String,
66        preview: String,
67    },
68    DelegationStarted {
69        from_org: String,
70        to_org: String,
71        task: String,
72    },
73    DelegationCompleted {
74        delegation_id: String,
75        plan_id: i64,
76        peer_name: String,
77    },
78
79    // Agent lifecycle
80    AgentOnline {
81        name: String,
82        org: String,
83        node: String,
84    },
85    AgentOffline {
86        name: String,
87        reason: String,
88    },
89
90    // System
91    HealthDegraded {
92        module: String,
93        reason: String,
94    },
95    BudgetAlert {
96        org: String,
97        spent: f64,
98        limit: f64,
99    },
100    ExtensionLoaded {
101        id: String,
102        version: String,
103    },
104
105    // Workspace awareness
106    FilesClaimed {
107        task_id: i64,
108        agent: String,
109        file_paths: Vec<String>,
110    },
111    FilesReleased {
112        task_id: i64,
113        file_paths: Vec<String>,
114    },
115
116    // Org knowledge queries
117    OrgAsked {
118        org_id: String,
119        question: String,
120        intent: String,
121        escalated: bool,
122        latency_ms: u64,
123    },
124}
125
126/// Filter for subscribing to events.
127#[derive(Debug, Clone)]
128pub struct EventFilter {
129    /// Event type prefix (e.g. "Task" matches TaskAssigned, TaskCompleted).
130    pub kind_prefix: Option<String>,
131    /// Only events from this org.
132    pub org: Option<String>,
133    /// Only events from this actor.
134    pub actor: Option<String>,
135}
136
137/// Trait for publishing domain events. Implemented by the IPC EventBus.
138/// Extensions retrieve `Arc<dyn DomainEventSink>` from AppContext to emit events.
139pub trait DomainEventSink: Send + Sync {
140    fn emit(&self, event: DomainEvent);
141}
142
143/// Helper: create and emit a simple domain event.
144pub fn make_event(actor_name: &str, kind: EventKind, context: EventContext) -> DomainEvent {
145    DomainEvent {
146        actor: ActorName {
147            name: actor_name.to_string(),
148            org: None,
149            node: None,
150        },
151        kind,
152        timestamp: Utc::now(),
153        context,
154    }
155}