use crate::types::{AgentId, TaskId};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DelegatedTaskState {
Pending,
Accepted,
Completed,
Failed,
}
impl std::fmt::Display for DelegatedTaskState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Pending => write!(f, "pending"),
Self::Accepted => write!(f, "accepted"),
Self::Completed => write!(f, "completed"),
Self::Failed => write!(f, "failed"),
}
}
}
#[derive(Debug, Clone)]
pub struct DelegatedTask {
pub task_id: TaskId,
pub delegated_to: AgentId,
pub task_type: String,
pub state: DelegatedTaskState,
pub created_at: Instant,
pub deadline: Option<Duration>,
pub result: Option<serde_json::Value>,
pub error: Option<String>,
}
impl DelegatedTask {
#[must_use]
pub fn new(task_id: TaskId, delegated_to: AgentId, task_type: String) -> Self {
Self {
task_id,
delegated_to,
task_type,
state: DelegatedTaskState::Pending,
created_at: Instant::now(),
deadline: None,
result: None,
error: None,
}
}
#[must_use]
pub fn with_deadline(mut self, deadline: Duration) -> Self {
self.deadline = Some(deadline);
self
}
pub fn accept(&mut self) {
self.state = DelegatedTaskState::Accepted;
}
pub fn complete(&mut self, result: serde_json::Value) {
self.state = DelegatedTaskState::Completed;
self.result = Some(result);
}
pub fn fail(&mut self, error: impl Into<String>) {
self.state = DelegatedTaskState::Failed;
self.error = Some(error.into());
}
#[must_use]
pub fn is_overdue(&self) -> bool {
if let Some(deadline) = self.deadline {
self.created_at.elapsed() > deadline
} else {
false
}
}
#[must_use]
pub fn is_terminal(&self) -> bool {
matches!(
self.state,
DelegatedTaskState::Completed | DelegatedTaskState::Failed
)
}
}
#[derive(Debug, Clone)]
pub struct IncomingTaskInfo {
pub task_id: TaskId,
pub from: AgentId,
pub task_type: String,
pub received_at: Instant,
pub accepted: bool,
}
#[derive(Debug, Clone, Default)]
pub struct DelegationTracker {
outgoing: HashMap<TaskId, DelegatedTask>,
incoming: HashMap<TaskId, IncomingTaskInfo>,
}
impl DelegationTracker {
#[must_use]
pub fn new() -> Self {
Self {
outgoing: HashMap::new(),
incoming: HashMap::new(),
}
}
pub fn track_outgoing(&mut self, task: DelegatedTask) {
self.outgoing.insert(task.task_id.clone(), task);
}
pub fn get_outgoing_mut(&mut self, task_id: &TaskId) -> Option<&mut DelegatedTask> {
self.outgoing.get_mut(task_id)
}
#[must_use]
pub fn get_outgoing(&self, task_id: &TaskId) -> Option<&DelegatedTask> {
self.outgoing.get(task_id)
}
pub fn track_incoming(&mut self, task_id: TaskId, from: AgentId, task_type: String) {
self.incoming.insert(
task_id.clone(),
IncomingTaskInfo {
task_id,
from,
task_type,
received_at: Instant::now(),
accepted: false,
},
);
}
pub fn accept_incoming(&mut self, task_id: &TaskId) -> bool {
if let Some(info) = self.incoming.get_mut(task_id) {
info.accepted = true;
true
} else {
false
}
}
pub fn remove_incoming(&mut self, task_id: &TaskId) -> Option<IncomingTaskInfo> {
self.incoming.remove(task_id)
}
#[must_use]
pub fn get_incoming(&self, task_id: &TaskId) -> Option<&IncomingTaskInfo> {
self.incoming.get(task_id)
}
#[must_use]
pub fn pending_outgoing_count(&self) -> usize {
self.outgoing.values().filter(|t| !t.is_terminal()).count()
}
#[must_use]
pub fn pending_incoming_count(&self) -> usize {
self.incoming.values().filter(|t| !t.accepted).count()
}
pub fn cleanup_completed(&mut self) {
self.outgoing.retain(|_, t| !t.is_terminal());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn delegated_task_state_display() {
assert_eq!(DelegatedTaskState::Pending.to_string(), "pending");
assert_eq!(DelegatedTaskState::Accepted.to_string(), "accepted");
assert_eq!(DelegatedTaskState::Completed.to_string(), "completed");
assert_eq!(DelegatedTaskState::Failed.to_string(), "failed");
}
#[test]
fn delegated_task_creation() {
let task_id = TaskId::new();
let agent_id = AgentId::new();
let task = DelegatedTask::new(task_id.clone(), agent_id.clone(), "code_review".to_string());
assert_eq!(task.task_id, task_id);
assert_eq!(task.delegated_to, agent_id);
assert_eq!(task.task_type, "code_review");
assert_eq!(task.state, DelegatedTaskState::Pending);
assert!(task.deadline.is_none());
assert!(task.result.is_none());
assert!(task.error.is_none());
}
#[test]
fn delegated_task_with_deadline() {
let task_id = TaskId::new();
let agent_id = AgentId::new();
let task = DelegatedTask::new(task_id, agent_id, "test".to_string())
.with_deadline(Duration::from_secs(60));
assert_eq!(task.deadline, Some(Duration::from_secs(60)));
}
#[test]
fn delegated_task_accept() {
let task_id = TaskId::new();
let agent_id = AgentId::new();
let mut task = DelegatedTask::new(task_id, agent_id, "test".to_string());
task.accept();
assert_eq!(task.state, DelegatedTaskState::Accepted);
}
#[test]
fn delegated_task_complete() {
let task_id = TaskId::new();
let agent_id = AgentId::new();
let mut task = DelegatedTask::new(task_id, agent_id, "test".to_string());
let result = serde_json::json!({"success": true});
task.complete(result.clone());
assert_eq!(task.state, DelegatedTaskState::Completed);
assert_eq!(task.result, Some(result));
assert!(task.is_terminal());
}
#[test]
fn delegated_task_fail() {
let task_id = TaskId::new();
let agent_id = AgentId::new();
let mut task = DelegatedTask::new(task_id, agent_id, "test".to_string());
task.fail("something went wrong");
assert_eq!(task.state, DelegatedTaskState::Failed);
assert_eq!(task.error, Some("something went wrong".to_string()));
assert!(task.is_terminal());
}
#[test]
fn delegated_task_is_overdue() {
let task_id = TaskId::new();
let agent_id = AgentId::new();
let task = DelegatedTask::new(task_id.clone(), agent_id.clone(), "test".to_string())
.with_deadline(Duration::from_millis(1));
std::thread::sleep(Duration::from_millis(5));
assert!(task.is_overdue());
let no_deadline_task = DelegatedTask::new(task_id, agent_id, "test".to_string());
assert!(!no_deadline_task.is_overdue());
}
#[test]
fn tracker_outgoing_tasks() {
let mut tracker = DelegationTracker::new();
let task_id = TaskId::new();
let agent_id = AgentId::new();
let task = DelegatedTask::new(task_id.clone(), agent_id.clone(), "code_review".to_string());
tracker.track_outgoing(task);
assert!(tracker.get_outgoing(&task_id).is_some());
assert_eq!(tracker.pending_outgoing_count(), 1);
let task = tracker.get_outgoing_mut(&task_id).unwrap();
task.complete(serde_json::json!({}));
assert_eq!(tracker.pending_outgoing_count(), 0);
}
#[test]
fn tracker_incoming_tasks() {
let mut tracker = DelegationTracker::new();
let task_id = TaskId::new();
let from_agent = AgentId::new();
tracker.track_incoming(
task_id.clone(),
from_agent.clone(),
"code_review".to_string(),
);
assert!(tracker.get_incoming(&task_id).is_some());
assert_eq!(tracker.pending_incoming_count(), 1);
assert!(tracker.accept_incoming(&task_id));
assert_eq!(tracker.pending_incoming_count(), 0);
let unknown_id = TaskId::new();
assert!(!tracker.accept_incoming(&unknown_id));
}
#[test]
fn tracker_remove_incoming() {
let mut tracker = DelegationTracker::new();
let task_id = TaskId::new();
let from_agent = AgentId::new();
tracker.track_incoming(task_id.clone(), from_agent, "test".to_string());
assert!(tracker.get_incoming(&task_id).is_some());
let removed = tracker.remove_incoming(&task_id);
assert!(removed.is_some());
assert!(tracker.get_incoming(&task_id).is_none());
}
#[test]
fn tracker_cleanup_completed() {
let mut tracker = DelegationTracker::new();
let task_id1 = TaskId::new();
let task_id2 = TaskId::new();
let agent_id = AgentId::new();
let task1 = DelegatedTask::new(task_id1.clone(), agent_id.clone(), "test1".to_string());
let task2 = DelegatedTask::new(task_id2.clone(), agent_id, "test2".to_string());
tracker.track_outgoing(task1);
tracker.track_outgoing(task2);
tracker
.get_outgoing_mut(&task_id1)
.unwrap()
.complete(serde_json::json!({}));
tracker.cleanup_completed();
assert!(tracker.get_outgoing(&task_id1).is_none());
assert!(tracker.get_outgoing(&task_id2).is_some());
}
}