use std::sync::{Arc, Mutex}; use super::event_store::EventStore; use super::models::*; use super::state_machine::{StateError, StateMachine}; /// Global task queue ordered by priority. pub struct TaskQueue { sm: Arc, store: Arc>, } impl TaskQueue { pub fn new(sm: Arc, store: Arc>) -> Self { Self { sm, store } } /// Enqueue a new task (status = created). pub async fn enqueue(&self, task: Task) -> Result { self.sm.create_task(&task).await } /// M8: Dequeue the highest-priority task matching capabilities. /// Atomically transitions to `Assigned` inside a single DB transaction /// via `dequeue_and_assign`, preventing concurrent dequeue of the same task. pub async fn dequeue( &self, required_capabilities: &[String], agent_id: Option<&str>, ) -> Result, StateError> { let caps = required_capabilities.to_vec(); let agent_id_owned = agent_id.map(String::from); let store = self.store.clone(); tokio::task::spawn_blocking(move || -> Result, StateError> { let mut store = store.lock().map_err(|e| StateError::Poisoned(e.to_string()))?; let now = chrono::Utc::now(); let event = TaskEvent { event_id: uuid::Uuid::new_v4().to_string(), // task_id filled inside dequeue_and_assign task_id: String::new(), event_type: "task.assigned".into(), agent_id: agent_id_owned.clone(), timestamp: now, payload: serde_json::json!({ "from_status": "created", "to_status": "assigned", "reason": "dequeued", }), }; Ok(store.dequeue_and_assign( &caps, agent_id_owned.as_deref(), now.to_rfc3339(), &event, )?) }) .await .map_err(StateError::Join)? } /// Re-queue a failed/agent_lost task (delegates to state machine transition). pub async fn requeue(&self, task_id: &str) -> Result { self.sm .transition(task_id, TaskStatus::Assigned, None, "re-queued after failure") .await } }