use tokio::sync::broadcast;
use crate::hooks::WorktreeInfo;
use crate::review::ReviewRequest;
use super::core::TmaiCore;
#[derive(Debug, Clone, serde::Serialize)]
#[serde(tag = "type")]
pub enum CoreEvent {
AgentsUpdated,
AgentStatusChanged {
target: String,
old_status: String,
new_status: String,
},
AgentAppeared {
target: String,
},
AgentDisappeared {
target: String,
},
TeamsUpdated,
TeammateIdle {
target: String,
team_name: String,
member_name: String,
},
TaskCreated {
team_name: String,
task_id: String,
task_subject: String,
},
TaskCompleted {
team_name: String,
task_id: String,
task_subject: String,
},
ConfigChanged {
target: String,
source: String,
file_path: String,
},
WorktreeCreated {
target: String,
worktree: Option<WorktreeInfo>,
},
WorktreeRemoved {
target: String,
worktree: Option<WorktreeInfo>,
},
InstructionsLoaded {
target: String,
},
AgentStopped {
target: String,
cwd: String,
last_assistant_message: Option<String>,
},
ContextCompacting {
target: String,
compaction_count: u32,
},
ReviewReady {
request: ReviewRequest,
},
ReviewLaunched {
source_target: String,
review_target: String,
},
ReviewCompleted {
source_target: String,
summary: String,
},
WorktreeSetupCompleted {
worktree_path: String,
branch: String,
},
WorktreeSetupFailed {
worktree_path: String,
branch: String,
error: String,
},
PromptReady {
target: String,
prompt: String,
},
UsageUpdated,
ToolCallDeferred {
defer_id: u64,
target: String,
tool_name: String,
},
RebaseSucceeded {
branch: String,
worktree_path: String,
},
RebaseConflict {
branch: String,
worktree_path: String,
error: String,
},
ToolCallResolved {
defer_id: u64,
target: String,
decision: String,
resolved_by: String,
},
}
impl TmaiCore {
pub fn subscribe(&self) -> broadcast::Receiver<CoreEvent> {
self.event_sender().subscribe()
}
pub fn notify_agents_updated(&self) {
let _ = self.event_sender().send(CoreEvent::AgentsUpdated);
}
pub fn notify_teams_updated(&self) {
let _ = self.event_sender().send(CoreEvent::TeamsUpdated);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::builder::TmaiCoreBuilder;
use crate::config::Settings;
#[tokio::test]
async fn test_subscribe_receives_events() {
let core = TmaiCoreBuilder::new(Settings::default()).build();
let mut rx = core.subscribe();
let tx = core.event_sender();
tx.send(CoreEvent::AgentsUpdated).unwrap();
let event = rx.recv().await.unwrap();
assert!(matches!(event, CoreEvent::AgentsUpdated));
}
#[tokio::test]
async fn test_subscribe_multiple_receivers() {
let core = TmaiCoreBuilder::new(Settings::default()).build();
let mut rx1 = core.subscribe();
let mut rx2 = core.subscribe();
let tx = core.event_sender();
tx.send(CoreEvent::TeamsUpdated).unwrap();
let e1 = rx1.recv().await.unwrap();
let e2 = rx2.recv().await.unwrap();
assert!(matches!(e1, CoreEvent::TeamsUpdated));
assert!(matches!(e2, CoreEvent::TeamsUpdated));
}
#[tokio::test]
async fn test_notify_agents_updated() {
let core = TmaiCoreBuilder::new(Settings::default()).build();
let mut rx = core.subscribe();
core.notify_agents_updated();
let event = rx.recv().await.unwrap();
assert!(matches!(event, CoreEvent::AgentsUpdated));
}
#[tokio::test]
async fn test_notify_teams_updated() {
let core = TmaiCoreBuilder::new(Settings::default()).build();
let mut rx = core.subscribe();
core.notify_teams_updated();
let event = rx.recv().await.unwrap();
assert!(matches!(event, CoreEvent::TeamsUpdated));
}
#[test]
fn test_notify_no_subscribers() {
let core = TmaiCoreBuilder::new(Settings::default()).build();
core.notify_agents_updated();
core.notify_teams_updated();
}
}