Skip to main content

task_graph_mcp/
types.rs

1//! Core types for the Task Graph MCP Server.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Worker (session-based) - represents a connected worker.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Worker {
9    pub id: String,
10    pub tags: Vec<String>,
11    pub max_claims: i32,
12    pub registered_at: i64,
13    pub last_heartbeat: i64,
14}
15
16/// Worker info with additional runtime details for list_workers.
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct WorkerInfo {
19    pub id: String,
20    pub tags: Vec<String>,
21    pub max_claims: i32,
22    pub claim_count: i32,
23    pub current_thought: Option<String>,
24    pub registered_at: i64,
25    pub last_heartbeat: i64,
26}
27
28/// Task priority as an integer (higher = more important).
29/// Range: 0-10, where 10 is highest priority. Default is 5.
30pub type Priority = i32;
31
32/// Default priority (middle of 0-10 range).
33pub const PRIORITY_DEFAULT: Priority = 5;
34
35/// Parse a priority value, clamping to 0-10 range.
36pub fn parse_priority(s: &str) -> Priority {
37    s.parse().unwrap_or(PRIORITY_DEFAULT).clamp(0, 10)
38}
39
40/// Clamp priority to valid range.
41pub fn clamp_priority(p: Priority) -> Priority {
42    p.clamp(0, 10)
43}
44
45/// A task in the task graph.
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct Task {
48    pub id: String,
49    pub title: String,
50    pub description: Option<String>,
51    pub status: String,
52    pub priority: Priority,
53    pub worker_id: Option<String>,
54    pub claimed_at: Option<i64>,
55
56    // Affinity (tag-based claiming requirements)
57    pub needed_tags: Vec<String>,
58    pub wanted_tags: Vec<String>,
59
60    // Categorization/discovery tags
61    pub tags: Vec<String>,
62
63    // Estimation & tracking
64    pub points: Option<i32>,
65    pub time_estimate_ms: Option<i64>,
66    pub time_actual_ms: Option<i64>,
67    pub started_at: Option<i64>,
68    pub completed_at: Option<i64>,
69
70    // Live status
71    pub current_thought: Option<String>,
72
73    // Cost accounting
74    pub cost_usd: f64,
75    /// Fixed array of 8 integer metrics [metric_0..metric_7], aggregated on update
76    pub metrics: [i64; 8],
77
78    pub created_at: i64,
79    pub updated_at: i64,
80}
81
82/// A task with its children for tree operations.
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct TaskTree {
85    #[serde(flatten)]
86    pub task: Task,
87    pub children: Vec<TaskTree>,
88}
89
90/// Input for creating a task tree.
91/// Supports all fields from task creation, plus tree-specific fields.
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct TaskTreeInput {
94    /// Reference to an existing task ID to include in the tree.
95    /// If set, this node references an existing task rather than creating a new one.
96    /// Other fields are ignored when ref is set.
97    #[serde(rename = "ref")]
98    pub ref_id: Option<String>,
99
100    /// Custom task ID (optional, UUID7 generated if not provided).
101    /// Ignored if ref is set.
102    pub id: Option<String>,
103
104    /// Task title (required for new tasks, optional if ref is set).
105    #[serde(default)]
106    pub title: String,
107
108    /// Task description.
109    pub description: Option<String>,
110
111    /// Task priority.
112    pub priority: Option<Priority>,
113
114    /// Story points / complexity estimate.
115    pub points: Option<i32>,
116
117    /// Estimated duration in milliseconds.
118    pub time_estimate_ms: Option<i64>,
119
120    /// Tags that claiming agent must have ALL of (AND logic).
121    pub needed_tags: Option<Vec<String>>,
122
123    /// Tags that claiming agent must have AT LEAST ONE of (OR logic).
124    pub wanted_tags: Option<Vec<String>>,
125
126    /// Categorization/discovery tags for the task.
127    pub tags: Option<Vec<String>>,
128
129    /// Child nodes in the tree.
130    #[serde(default)]
131    pub children: Vec<TaskTreeInput>,
132}
133
134/// A typed dependency between tasks.
135/// The dependency indicates that from_task_id affects to_task_id based on dep_type.
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct Dependency {
138    pub from_task_id: String,
139    pub to_task_id: String,
140    /// Dependency type: "blocks", "follows", "contains", or custom types.
141    pub dep_type: String,
142}
143
144/// An advisory file lock.
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct FileLock {
147    pub file_path: String,
148    pub worker_id: String,
149    pub reason: Option<String>,
150    pub locked_at: i64,
151    pub task_id: Option<String>,
152}
153
154/// A claim event for file coordination tracking.
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct ClaimEvent {
157    pub id: i64,
158    pub file_path: String,
159    pub worker_id: String,
160    pub event: ClaimEventType,
161    pub reason: Option<String>,
162    pub timestamp: i64,
163    pub end_timestamp: Option<i64>,
164    /// For release events: the ID of the corresponding claim event.
165    pub claim_id: Option<i64>,
166}
167
168/// A task state transition event for time tracking.
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct TaskStateEvent {
171    pub id: i64,
172    pub task_id: String,
173    pub worker_id: Option<String>,
174    pub event: String,
175    pub reason: Option<String>,
176    pub timestamp: i64,
177    pub end_timestamp: Option<i64>,
178}
179
180/// Type of claim event.
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
182#[serde(rename_all = "snake_case")]
183pub enum ClaimEventType {
184    Claimed,
185    Released,
186}
187
188impl ClaimEventType {
189    pub fn as_str(&self) -> &'static str {
190        match self {
191            ClaimEventType::Claimed => "claimed",
192            ClaimEventType::Released => "released",
193        }
194    }
195
196    pub fn parse(s: &str) -> Option<Self> {
197        match s {
198            "claimed" => Some(ClaimEventType::Claimed),
199            "released" => Some(ClaimEventType::Released),
200            _ => None,
201        }
202    }
203}
204
205/// Result of polling claim updates.
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct ClaimUpdates {
208    pub new_claims: Vec<ClaimEvent>,
209    pub dropped_claims: Vec<ClaimEvent>,
210    pub sequence: i64,
211}
212
213/// An attachment on a task.
214/// Primary key is (task_id, order_index).
215/// If file_path is set, content is stored in the referenced file; otherwise content is inline.
216#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct Attachment {
218    pub task_id: String,
219    pub order_index: i32,
220    pub name: String,
221    pub mime_type: String,
222    pub content: String,
223    /// Path to the file containing the content (relative to media dir or absolute).
224    /// If set, content is read from this file; if None, content is stored inline.
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub file_path: Option<String>,
227    pub created_at: i64,
228}
229
230/// Attachment metadata (without content).
231/// Primary key is (task_id, order_index).
232#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct AttachmentMeta {
234    pub task_id: String,
235    pub order_index: i32,
236    pub name: String,
237    pub mime_type: String,
238    /// Path to the file containing the content (if stored as file).
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub file_path: Option<String>,
241    pub created_at: i64,
242}
243
244/// Aggregate statistics.
245#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct Stats {
247    pub total_tasks: i64,
248    /// Task counts by state (dynamic based on config).
249    pub tasks_by_status: HashMap<String, i64>,
250    pub total_points: i64,
251    pub completed_points: i64,
252    pub total_time_estimate_ms: i64,
253    pub total_time_actual_ms: i64,
254    pub total_cost_usd: f64,
255    /// Aggregated metrics [metric_0..metric_7]
256    pub total_metrics: [i64; 8],
257}
258
259/// Compact task representation for list views.
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct TaskSummary {
262    pub id: String,
263    pub title: String,
264    pub status: String,
265    pub priority: Priority,
266    pub worker_id: Option<String>,
267    pub points: Option<i32>,
268    pub current_thought: Option<String>,
269}
270
271/// Result of scanning the task graph from a starting task.
272/// Contains tasks organized by traversal direction.
273#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct ScanResult {
275    /// The task that was scanned from
276    pub root: Task,
277    /// Tasks that block this task (predecessors via blocks/follows)
278    pub before: Vec<Task>,
279    /// Tasks that this task blocks (successors via blocks/follows)
280    pub after: Vec<Task>,
281    /// Parent chain (ancestors via contains)
282    pub above: Vec<Task>,
283    /// Children tree (descendants via contains)
284    pub below: Vec<Task>,
285}
286
287/// Summary of disconnect operation.
288#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct DisconnectSummary {
290    /// Number of tasks that were released.
291    pub tasks_released: i32,
292    /// Number of file locks that were released.
293    pub files_released: i32,
294    /// The final status applied to released tasks.
295    pub final_status: String,
296}
297
298/// Summary of stale worker cleanup operation.
299#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct CleanupSummary {
301    /// Number of stale workers evicted.
302    pub workers_evicted: i32,
303    /// Total number of tasks released across all evicted workers.
304    pub tasks_released: i32,
305    /// Total number of file locks released across all evicted workers.
306    pub files_released: i32,
307    /// The final status applied to released tasks.
308    pub final_status: String,
309    /// IDs of evicted workers.
310    pub evicted_worker_ids: Vec<String>,
311}
312
313#[cfg(test)]
314mod tests {
315    // Priority tests removed - Priority is now a type alias for i32
316}