1use serde::{Deserialize, Serialize};
2use time::OffsetDateTime;
3
4use crate::events::{ThreadId, TurnId};
5use crate::policy_mode::PolicyMode;
6
7pub type TeamId = String;
8pub type TeamMemberId = String;
9pub type TeamTaskId = String;
10pub type TeamMessageId = String;
11
12#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
13#[serde(rename_all = "snake_case")]
14pub enum AgentTeamDisplayMode {
15 #[default]
16 Auto,
17 #[serde(alias = "in-process", alias = "inprocess")]
18 InProcess,
19 Tmux,
20 #[serde(alias = "iterm")]
21 Iterm2,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
25#[serde(rename_all = "camelCase")]
26pub struct AgentTeamConfig {
27 pub enabled: bool,
28 pub display_mode: AgentTeamDisplayMode,
29 pub default_teammate_model: Option<String>,
30 pub require_plan_approval: bool,
31 pub max_teammates: usize,
32 pub split_panes: AgentTeamSplitPaneConfig,
33}
34
35impl Default for AgentTeamConfig {
36 fn default() -> Self {
37 Self {
38 enabled: false,
39 display_mode: AgentTeamDisplayMode::Auto,
40 default_teammate_model: Some("lead".to_string()),
41 require_plan_approval: false,
42 max_teammates: 5,
43 split_panes: AgentTeamSplitPaneConfig::default(),
44 }
45 }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
49#[serde(rename_all = "camelCase")]
50pub struct AgentTeamSplitPaneConfig {
51 pub reuse_existing_tmux_session: bool,
52 pub tmux_command: String,
53 pub iterm2_command: String,
54}
55
56impl Default for AgentTeamSplitPaneConfig {
57 fn default() -> Self {
58 Self {
59 reuse_existing_tmux_session: true,
60 tmux_command: "tmux".to_string(),
61 iterm2_command: "it2".to_string(),
62 }
63 }
64}
65
66#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
67#[serde(rename_all = "snake_case")]
68pub enum TeamMemberRole {
69 Lead,
70 Teammate,
71}
72
73#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
74#[serde(rename_all = "snake_case")]
75pub enum TeamMemberStatus {
76 Idle,
77 Running,
78 Blocked,
79 Completed,
80 Failed,
81 Interrupted,
82 Closed,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
86#[serde(rename_all = "camelCase")]
87pub struct TeamMemberDescriptor {
88 pub id: TeamMemberId,
89 pub role: TeamMemberRole,
90 pub name: String,
91 pub thread_id: ThreadId,
92 pub current_turn_id: Option<TurnId>,
93 pub model_provider: Option<String>,
94 pub model: Option<String>,
95 pub policy_mode: PolicyMode,
96 pub status: TeamMemberStatus,
97 pub pane_id: Option<String>,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
101#[serde(rename_all = "camelCase")]
102pub struct TeamMailboxMessage {
103 pub id: TeamMessageId,
104 pub team_id: TeamId,
105 pub from_member_id: Option<TeamMemberId>,
106 pub to_member_id: TeamMemberId,
107 pub text: String,
108 #[serde(with = "time::serde::rfc3339")]
109 pub timestamp: OffsetDateTime,
110}
111
112#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
113#[serde(rename_all = "snake_case")]
114pub enum TeamTaskStatus {
115 Pending,
116 InProgress,
117 Completed,
118 Failed,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
122#[serde(rename_all = "camelCase")]
123pub struct TeamTaskDescriptor {
124 pub id: TeamTaskId,
125 pub title: String,
126 pub status: TeamTaskStatus,
127 pub assignee_member_id: Option<TeamMemberId>,
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn team_display_mode_accepts_snake_case_and_hyphenated_values() {
136 let snake: AgentTeamDisplayMode = serde_json::from_str("\"in_process\"").unwrap();
137 let hyphen: AgentTeamDisplayMode = serde_json::from_str("\"in-process\"").unwrap();
138
139 assert_eq!(snake, AgentTeamDisplayMode::InProcess);
140 assert_eq!(hyphen, AgentTeamDisplayMode::InProcess);
141 assert_eq!(
142 serde_json::to_string(&AgentTeamDisplayMode::Tmux).unwrap(),
143 "\"tmux\""
144 );
145 }
146
147 #[test]
148 fn agent_team_config_defaults_to_disabled_auto() {
149 let config = AgentTeamConfig::default();
150
151 assert!(!config.enabled);
152 assert_eq!(config.display_mode, AgentTeamDisplayMode::Auto);
153 assert_eq!(config.max_teammates, 5);
154 assert!(config.split_panes.reuse_existing_tmux_session);
155 }
156}