1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
6pub struct ProjectId(pub String);
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct WorkstreamId(pub String);
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub struct AgentId(pub String);
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct ShellId(pub String);
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
18#[serde(rename_all = "lowercase")]
19pub enum AgentType {
20 Claude,
21 Codex,
22}
23
24impl fmt::Display for AgentType {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 match self {
27 AgentType::Claude => write!(f, "Claude"),
28 AgentType::Codex => write!(f, "Codex"),
29 }
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34pub enum AgentState {
35 Idle,
36 Running,
37 NeedsIntervention,
38}
39
40impl fmt::Display for ProjectId {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 write!(f, "{}", self.0)
43 }
44}
45
46impl fmt::Display for WorkstreamId {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 write!(f, "{}", self.0)
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
53pub enum WorkstreamState {
54 Active,
55 Paused,
56 Completed,
57 Archived,
58}
59
60pub fn validate_slug(name: &str) -> bool {
63 !name.is_empty()
64 && name.chars().next().unwrap().is_ascii_alphanumeric()
65 && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-')
66}
67
68impl fmt::Display for WorkstreamState {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match self {
71 WorkstreamState::Active => write!(f, "active"),
72 WorkstreamState::Paused => write!(f, "paused"),
73 WorkstreamState::Completed => write!(f, "completed"),
74 WorkstreamState::Archived => write!(f, "archived"),
75 }
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn newtype_construction() {
85 let pid = ProjectId("my-project".into());
86 assert_eq!(pid.0, "my-project");
87
88 let wid = WorkstreamId("ws-1".into());
89 assert_eq!(wid.0, "ws-1");
90
91 let aid = AgentId("agent-0".into());
92 assert_eq!(aid.0, "agent-0");
93
94 let sid = ShellId("shell-0".into());
95 assert_eq!(sid.0, "shell-0");
96 }
97
98 #[test]
99 fn agent_type_display() {
100 assert_eq!(AgentType::Claude.to_string(), "Claude");
101 assert_eq!(AgentType::Codex.to_string(), "Codex");
102 }
103
104 #[test]
105 fn newtypes_are_eq_and_hash() {
106 use std::collections::HashSet;
107 let mut set = HashSet::new();
108 set.insert(ProjectId("a".into()));
109 set.insert(ProjectId("b".into()));
110 set.insert(ProjectId("a".into()));
111 assert_eq!(set.len(), 2);
112 }
113}