Skip to main content

opengate_models/
lib.rs

1use serde::{Deserialize, Serialize};
2
3// --- Enums ---
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
6#[serde(rename_all = "snake_case")]
7pub enum TaskStatus {
8    Backlog,
9    Todo,
10    InProgress,
11    Review,
12    Blocked,
13    Done,
14    Cancelled,
15    Handoff,
16}
17
18impl TaskStatus {
19    pub fn as_str(&self) -> &'static str {
20        match self {
21            TaskStatus::Backlog => "backlog",
22            TaskStatus::Todo => "todo",
23            TaskStatus::InProgress => "in_progress",
24            TaskStatus::Review => "review",
25            TaskStatus::Blocked => "blocked",
26            TaskStatus::Done => "done",
27            TaskStatus::Cancelled => "cancelled",
28            TaskStatus::Handoff => "handoff",
29        }
30    }
31
32    #[allow(clippy::should_implement_trait)]
33    pub fn from_str(s: &str) -> Option<Self> {
34        match s {
35            "backlog" => Some(TaskStatus::Backlog),
36            "todo" => Some(TaskStatus::Todo),
37            "in_progress" => Some(TaskStatus::InProgress),
38            "review" => Some(TaskStatus::Review),
39            "blocked" => Some(TaskStatus::Blocked),
40            "done" => Some(TaskStatus::Done),
41            "cancelled" => Some(TaskStatus::Cancelled),
42            "handoff" => Some(TaskStatus::Handoff),
43            _ => None,
44        }
45    }
46
47    pub fn valid_transitions(&self) -> Vec<TaskStatus> {
48        match self {
49            TaskStatus::Backlog => vec![
50                TaskStatus::Todo,
51                TaskStatus::InProgress,
52                TaskStatus::Cancelled,
53            ],
54            TaskStatus::Todo => vec![
55                TaskStatus::InProgress,
56                TaskStatus::Blocked,
57                TaskStatus::Cancelled,
58            ],
59            TaskStatus::InProgress => vec![
60                TaskStatus::Review,
61                TaskStatus::Done,
62                TaskStatus::Blocked,
63                TaskStatus::Cancelled,
64                TaskStatus::Handoff,
65            ],
66            TaskStatus::Review => vec![TaskStatus::Done, TaskStatus::InProgress],
67            TaskStatus::Blocked => vec![
68                TaskStatus::Todo,
69                TaskStatus::InProgress,
70                TaskStatus::Cancelled,
71            ],
72            TaskStatus::Done => vec![],
73            TaskStatus::Cancelled => vec![],
74            TaskStatus::Handoff => vec![TaskStatus::InProgress],
75        }
76    }
77
78    pub fn can_transition_to(&self, target: &TaskStatus) -> bool {
79        self.valid_transitions().contains(target)
80    }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
84#[serde(rename_all = "snake_case")]
85pub enum Priority {
86    Critical,
87    High,
88    Medium,
89    Low,
90}
91
92impl Priority {
93    pub fn as_str(&self) -> &'static str {
94        match self {
95            Priority::Critical => "critical",
96            Priority::High => "high",
97            Priority::Medium => "medium",
98            Priority::Low => "low",
99        }
100    }
101
102    #[allow(clippy::should_implement_trait)]
103    pub fn from_str(s: &str) -> Option<Self> {
104        match s {
105            "critical" => Some(Priority::Critical),
106            "high" => Some(Priority::High),
107            "medium" => Some(Priority::Medium),
108            "low" => Some(Priority::Low),
109            _ => None,
110        }
111    }
112
113    pub fn sort_order(&self) -> i32 {
114        match self {
115            Priority::Critical => 0,
116            Priority::High => 1,
117            Priority::Medium => 2,
118            Priority::Low => 3,
119        }
120    }
121}
122
123// --- Domain models ---
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct Project {
127    pub id: String,
128    pub name: String,
129    pub description: Option<String>,
130    pub status: String,
131    pub repo_url: Option<String>,
132    pub default_branch: Option<String>,
133    pub created_at: String,
134    pub updated_at: String,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct Task {
139    pub id: String,
140    pub project_id: String,
141    pub title: String,
142    pub description: Option<String>,
143    pub status: String,
144    pub priority: String,
145    pub assignee_type: Option<String>,
146    pub assignee_id: Option<String>,
147    pub context: Option<serde_json::Value>,
148    pub output: Option<serde_json::Value>,
149    pub tags: Vec<String>,
150    pub due_date: Option<String>,
151    pub reviewer_type: Option<String>,
152    pub reviewer_id: Option<String>,
153    pub status_history: Vec<StatusHistoryEntry>,
154    pub artifacts: Vec<TaskArtifact>,
155    /// ISO8601 datetime: task stays in backlog until this passes
156    pub scheduled_at: Option<String>,
157    /// JSON recurrence rule: {frequency, interval, cron, ...}
158    pub recurrence_rule: Option<serde_json::Value>,
159    /// Points to the original recurring task (parent)
160    pub recurrence_parent_id: Option<String>,
161    /// IDs of tasks this task depends on (loaded from task_dependencies)
162    pub dependencies: Vec<String>,
163    /// True if this task has blocking open questions
164    pub has_open_questions: bool,
165    /// ISO8601 timestamp: when the reviewer started reviewing this task
166    pub started_review_at: Option<String>,
167    pub created_by: String,
168    pub created_at: String,
169    pub updated_at: String,
170    #[serde(default, skip_serializing_if = "Vec::is_empty")]
171    pub activities: Vec<TaskActivity>,
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct StatusHistoryEntry {
176    pub status: String,
177    pub agent_id: Option<String>,
178    pub agent_type: Option<String>,
179    pub timestamp: String,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct TaskActivity {
184    pub id: String,
185    pub task_id: String,
186    pub author_type: String,
187    pub author_id: String,
188    pub content: String,
189    pub activity_type: String,
190    pub metadata: Option<serde_json::Value>,
191    pub created_at: String,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct Agent {
196    pub id: String,
197    pub name: String,
198    #[serde(skip_serializing)]
199    pub api_key_hash: String,
200    pub skills: Vec<String>,
201    pub description: Option<String>,
202    pub status: String,
203    pub max_concurrent_tasks: i64,
204    pub current_task_count: i64,
205    /// Count of tasks where this agent is reviewer and status = review
206    pub review_task_count: i64,
207    pub webhook_url: Option<String>,
208    /// Optional JSON array of event types to push via webhook. If null/empty, all events trigger push.
209    pub webhook_events: Option<Vec<String>>,
210    pub config: Option<serde_json::Value>,
211    pub model: Option<String>,
212    pub provider: Option<String>,
213    pub cost_tier: Option<String>,
214    pub capabilities: Vec<String>,
215    /// senior | mid | junior
216    pub seniority: String,
217    /// orchestrator | executor
218    pub role: String,
219    /// Minutes before agent is considered stale/offline (default: 30)
220    pub stale_timeout: i64,
221    pub last_seen_at: Option<String>,
222    pub created_at: String,
223    /// Optional owner (e.g. Clerk user_id). None in standalone OSS mode.
224    pub owner_id: Option<String>,
225    /// Free-form category tags (e.g. ["rust", "frontend", "devops"])
226    pub tags: Vec<String>,
227}
228
229// --- DTOs ---
230
231#[derive(Debug, Deserialize)]
232pub struct CreateProject {
233    pub name: String,
234    pub description: Option<String>,
235    pub repo_url: Option<String>,
236    pub default_branch: Option<String>,
237}
238
239#[derive(Debug, Deserialize)]
240pub struct UpdateProject {
241    pub name: Option<String>,
242    pub description: Option<String>,
243    pub status: Option<String>,
244    pub repo_url: Option<String>,
245    pub default_branch: Option<String>,
246}
247
248#[derive(Debug, Deserialize)]
249pub struct CreateTask {
250    pub title: String,
251    pub description: Option<String>,
252    pub priority: Option<String>,
253    pub tags: Option<Vec<String>>,
254    pub context: Option<serde_json::Value>,
255    pub output: Option<serde_json::Value>,
256    pub due_date: Option<String>,
257    pub assignee_type: Option<String>,
258    pub assignee_id: Option<String>,
259    /// ISO8601: defer task start until this datetime
260    pub scheduled_at: Option<String>,
261    /// Recurrence rule JSON
262    pub recurrence_rule: Option<serde_json::Value>,
263}
264
265#[derive(Debug, Deserialize)]
266pub struct UpdateTask {
267    pub title: Option<String>,
268    pub description: Option<String>,
269    pub status: Option<String>,
270    pub priority: Option<String>,
271    pub tags: Option<Vec<String>>,
272    pub context: Option<serde_json::Value>,
273    pub output: Option<serde_json::Value>,
274    pub due_date: Option<String>,
275    pub assignee_type: Option<String>,
276    pub assignee_id: Option<String>,
277    pub reviewer_type: Option<String>,
278    pub reviewer_id: Option<String>,
279    /// ISO8601: defer task start until this datetime
280    pub scheduled_at: Option<String>,
281    /// Recurrence rule JSON (set to null object to clear)
282    pub recurrence_rule: Option<serde_json::Value>,
283}
284
285/// Request to add dependencies to a task
286#[derive(Debug, Deserialize)]
287pub struct AddDependenciesRequest {
288    pub depends_on: Vec<String>,
289}
290
291/// Response for schedule endpoint — task with its scheduled_at
292#[derive(Debug, Serialize)]
293pub struct ScheduledTaskEntry {
294    pub id: String,
295    pub title: String,
296    pub status: String,
297    pub priority: String,
298    pub scheduled_at: String,
299    pub assignee_id: Option<String>,
300}
301
302#[derive(Debug, Deserialize)]
303pub struct CreateAgent {
304    pub name: String,
305    pub skills: Option<Vec<String>>,
306    pub model: Option<String>,
307    pub provider: Option<String>,
308    pub cost_tier: Option<String>,
309    pub capabilities: Option<Vec<String>>,
310    pub seniority: Option<String>,
311    pub role: Option<String>,
312    pub owner_id: Option<String>,
313}
314
315impl CreateAgent {
316    /// Builder for tests — single update point when schema changes.
317    pub fn new(name: impl Into<String>) -> Self {
318        Self {
319            name: name.into(),
320            skills: None,
321            model: None,
322            provider: None,
323            cost_tier: None,
324            capabilities: None,
325            seniority: None,
326            role: None,
327            owner_id: None,
328        }
329    }
330
331    pub fn with_skills(mut self, skills: Vec<String>) -> Self {
332        self.skills = Some(skills);
333        self
334    }
335
336    pub fn with_seniority(mut self, seniority: impl Into<String>) -> Self {
337        self.seniority = Some(seniority.into());
338        self
339    }
340
341    pub fn with_role(mut self, role: impl Into<String>) -> Self {
342        self.role = Some(role.into());
343        self
344    }
345
346    pub fn with_capabilities(mut self, capabilities: Vec<String>) -> Self {
347        self.capabilities = Some(capabilities);
348        self
349    }
350}
351
352#[derive(Debug, Deserialize)]
353pub struct RegisterAgentRequest {
354    pub name: String,
355    pub skills: Option<Vec<String>>,
356    pub setup_token: String,
357    pub model: Option<String>,
358    pub provider: Option<String>,
359    pub cost_tier: Option<String>,
360    pub capabilities: Option<Vec<String>>,
361    pub owner_id: Option<String>,
362}
363
364#[derive(Debug, Deserialize)]
365pub struct CreateActivity {
366    pub content: String,
367    pub activity_type: Option<String>,
368    pub metadata: Option<serde_json::Value>,
369}
370
371#[derive(Debug, Deserialize)]
372pub struct TaskFilters {
373    pub project_id: Option<String>,
374    pub status: Option<String>,
375    pub priority: Option<String>,
376    pub assignee_id: Option<String>,
377    pub tag: Option<String>,
378}
379
380#[derive(Debug, Deserialize)]
381pub struct BatchStatusUpdate {
382    pub updates: Vec<BatchStatusItem>,
383}
384
385#[derive(Debug, Deserialize)]
386pub struct BatchStatusItem {
387    pub task_id: String,
388    pub status: String,
389}
390
391#[derive(Debug, Serialize)]
392pub struct BatchResult {
393    pub succeeded: Vec<String>,
394    pub failed: Vec<BatchError>,
395}
396
397#[derive(Debug, Serialize)]
398pub struct BatchError {
399    pub task_id: String,
400    pub error: String,
401}
402
403#[derive(Debug, Serialize)]
404pub struct DashboardStats {
405    pub tasks_by_status: std::collections::HashMap<String, i64>,
406    pub total_tasks: i64,
407    pub active_agents: i64,
408    pub total_projects: i64,
409    pub recent_activity: Vec<TaskActivity>,
410}
411
412#[derive(Debug, Serialize)]
413pub struct ProjectWithStats {
414    pub project: Project,
415    pub task_count: i64,
416    pub tasks_by_status: std::collections::HashMap<String, i64>,
417}
418
419#[derive(Debug, Serialize)]
420pub struct AgentCreated {
421    pub agent: Agent,
422    pub api_key: String,
423}
424
425#[derive(Debug, Deserialize)]
426pub struct CompleteRequest {
427    pub summary: Option<String>,
428    pub output: Option<serde_json::Value>,
429}
430
431#[derive(Debug, Deserialize)]
432pub struct SubmitReviewRequest {
433    /// Optional summary of what was done (recorded as activity).
434    pub summary: Option<String>,
435    /// Explicit reviewer agent ID override. If omitted, one is auto-selected.
436    pub reviewer_id: Option<String>,
437}
438
439#[derive(Debug, Deserialize)]
440pub struct BlockRequest {
441    pub reason: Option<String>,
442}
443
444#[derive(Debug, Deserialize)]
445pub struct NextTaskQuery {
446    pub skills: Option<String>,
447}
448
449// --- Identity (from auth) ---
450
451#[derive(Debug, Clone)]
452pub enum Identity {
453    /// An agent authenticated via API key. `tenant_id` mirrors the agent's `owner_id`
454    /// and is used for multi-tenant data isolation — agents only see data belonging to
455    /// their owner. `None` means single-tenant / OSS mode (no filtering).
456    AgentIdentity {
457        id: String,
458        name: String,
459        tenant_id: Option<String>,
460    },
461    /// A human user. `tenant_id` is used for multi-tenant data isolation (e.g. in TaskForge).
462    /// The OSS single-tenant engine ignores it; multi-tenant backends filter by it.
463    Human {
464        id: String,
465        tenant_id: Option<String>,
466    },
467    Anonymous,
468}
469
470impl Identity {
471    pub fn author_type(&self) -> &'static str {
472        match self {
473            Identity::AgentIdentity { .. } => "agent",
474            Identity::Human { .. } => "human",
475            Identity::Anonymous => "system",
476        }
477    }
478
479    pub fn author_id(&self) -> &str {
480        match self {
481            Identity::AgentIdentity { id, .. } => id,
482            Identity::Human { id, .. } => id,
483            Identity::Anonymous => "system",
484        }
485    }
486
487    pub fn display_name(&self) -> &str {
488        match self {
489            Identity::AgentIdentity { name, .. } => name,
490            Identity::Human { id, .. } => id,
491            Identity::Anonymous => "system",
492        }
493    }
494
495    /// Returns the tenant id for multi-tenant isolation, if available.
496    /// Agents carry their owner's tenant_id; humans carry their own.
497    /// Returns `None` in single-tenant (OSS) mode — no filtering applied.
498    pub fn tenant_id(&self) -> Option<&str> {
499        match self {
500            Identity::AgentIdentity { tenant_id, .. } => tenant_id.as_deref(),
501            Identity::Human { tenant_id, .. } => tenant_id.as_deref(),
502            Identity::Anonymous => None,
503        }
504    }
505}
506
507// --- v2 DTOs ---
508
509#[derive(Debug, Deserialize)]
510pub struct UpdateAgent {
511    pub description: Option<String>,
512    pub skills: Option<Vec<String>>,
513    pub max_concurrent_tasks: Option<i64>,
514    pub webhook_url: Option<String>,
515    /// JSON array of event types to subscribe to for webhook push. null = all events.
516    pub webhook_events: Option<Vec<String>>,
517    pub config: Option<serde_json::Value>,
518    pub model: Option<String>,
519    pub provider: Option<String>,
520    pub cost_tier: Option<String>,
521    pub capabilities: Option<Vec<String>>,
522    pub seniority: Option<String>,
523    pub role: Option<String>,
524    /// Minutes before agent is considered stale (default: 30)
525    pub stale_timeout: Option<i64>,
526    /// Free-form category tags (e.g. ["rust", "frontend", "devops"])
527    pub tags: Option<Vec<String>>,
528}
529
530#[derive(Debug, Deserialize)]
531pub struct AssignRequest {
532    pub agent_id: String,
533}
534
535#[derive(Debug, Deserialize)]
536pub struct HandoffRequest {
537    pub to_agent_id: String,
538    pub summary: Option<String>,
539}
540
541#[derive(Debug, Deserialize)]
542pub struct ApproveRequest {
543    pub comment: Option<String>,
544}
545
546#[derive(Debug, Deserialize)]
547pub struct RequestChangesRequest {
548    pub comment: String,
549}
550
551// --- Knowledge Base ---
552
553/// Valid category values for knowledge entries.
554pub const VALID_CATEGORIES: &[&str] =
555    &["architecture", "pattern", "gotcha", "decision", "reference"];
556
557#[derive(Debug, Clone, Serialize, Deserialize)]
558pub struct KnowledgeEntry {
559    pub id: String,
560    pub project_id: String,
561    pub key: String,
562    pub title: String,
563    pub content: String,
564    pub metadata: Option<serde_json::Value>,
565    /// Tag list stored as JSON array in SQLite.
566    pub tags: Vec<String>,
567    /// Optional category: architecture | pattern | gotcha | decision | reference
568    pub category: Option<String>,
569    pub created_by_type: String,
570    pub created_by_id: String,
571    pub updated_at: String,
572    pub created_at: String,
573}
574
575#[derive(Debug, Deserialize)]
576pub struct UpsertKnowledge {
577    pub title: String,
578    pub content: String,
579    pub metadata: Option<serde_json::Value>,
580    /// Tags to attach (optional, defaults to empty list on create).
581    pub tags: Option<Vec<String>>,
582    /// Category: architecture | pattern | gotcha | decision | reference
583    pub category: Option<String>,
584}
585
586#[derive(Debug, Deserialize)]
587pub struct KnowledgeSearchQuery {
588    pub q: Option<String>,
589    pub prefix: Option<String>,
590    /// Comma-separated tags to filter by (OR match): ?tags=rust,performance
591    pub tags: Option<String>,
592    /// Filter by category: ?category=pattern
593    pub category: Option<String>,
594}
595
596// --- Task Artifacts ---
597
598pub const VALID_ARTIFACT_TYPES: &[&str] = &["url", "text", "json", "file"];
599
600#[derive(Debug, Clone, Serialize, Deserialize)]
601pub struct TaskArtifact {
602    pub id: String,
603    pub task_id: String,
604    pub name: String,
605    pub artifact_type: String,
606    pub value: String,
607    pub created_by_type: String,
608    pub created_by_id: String,
609    pub created_at: String,
610}
611
612#[derive(Debug, Deserialize)]
613pub struct CreateArtifact {
614    pub name: String,
615    pub artifact_type: String,
616    pub value: String,
617}
618
619#[derive(Debug, Deserialize)]
620pub struct UpdateArtifact {
621    /// Optional new name for the artifact.
622    pub name: Option<String>,
623    /// Optional new value. artifact_type is immutable after creation.
624    pub value: Option<String>,
625}
626
627// --- Webhook Log ---
628
629#[derive(Debug, Clone, Serialize, Deserialize)]
630pub struct WebhookLogEntry {
631    pub id: String,
632    pub agent_id: String,
633    pub event_type: String,
634    pub payload: serde_json::Value,
635    pub status: String,
636    pub attempts: i64,
637    pub last_attempt_at: Option<String>,
638    pub created_at: String,
639}
640
641// --- Notifications ---
642
643#[derive(Debug, Clone, Serialize, Deserialize)]
644pub struct Notification {
645    pub id: i64,
646    pub agent_id: String,
647    pub event_id: Option<i64>,
648    pub event_type: String,
649    pub title: String,
650    pub body: Option<String>,
651    pub read: bool,
652    /// Webhook delivery status: "delivered" | "failed" | null (not attempted)
653    pub webhook_status: Option<String>,
654    pub created_at: String,
655}
656
657/// Carries information about a newly-inserted notification that may need webhook delivery.
658#[derive(Debug, Clone)]
659pub struct PendingNotifWebhook {
660    pub agent_id: String,
661    pub notification_id: i64,
662    pub event_type: String,
663    pub title: String,
664    pub body: Option<String>,
665}
666
667#[derive(Debug, Deserialize)]
668pub struct NotificationQuery {
669    pub unread: Option<bool>,
670}
671
672// --- Pulse ---
673
674#[derive(Debug, Serialize)]
675pub struct PulseResponse {
676    pub active_tasks: Vec<PulseTask>,
677    pub blocked_tasks: Vec<PulseTask>,
678    pub pending_review: Vec<PulseTask>,
679    pub recently_completed: Vec<PulseTask>,
680    pub unread_events: i64,
681    pub agents: Vec<PulseAgent>,
682    pub recent_knowledge_updates: Vec<PulseKnowledge>,
683    /// Number of tasks currently blocked by unmet dependencies
684    pub blocked_by_deps: i64,
685}
686
687#[derive(Debug, Serialize)]
688pub struct PulseTask {
689    pub id: String,
690    pub title: String,
691    pub status: String,
692    pub priority: String,
693    pub assignee_name: Option<String>,
694    pub reviewer_name: Option<String>,
695    pub tags: Vec<String>,
696    pub updated_at: String,
697}
698
699#[derive(Debug, Serialize)]
700pub struct PulseAgent {
701    pub id: String,
702    pub name: String,
703    pub status: String,
704    pub seniority: String,
705    pub role: String,
706    pub current_task: Option<String>,
707    pub last_seen_at: Option<String>,
708}
709
710#[derive(Debug, Serialize)]
711pub struct PulseKnowledge {
712    pub key: String,
713    pub title: String,
714    pub category: Option<String>,
715    pub updated_at: String,
716}
717
718#[derive(Debug, Deserialize)]
719pub struct AgentQuery {
720    pub capability: Option<String>,
721    pub seniority: Option<String>,
722}
723
724#[derive(Debug, Deserialize)]
725pub struct AgentMatchQuery {
726    pub capability: Option<String>,
727    pub seniority: Option<String>,
728    pub role: Option<String>,
729}
730
731/// Strategy for auto-assigning agents based on capability, seniority, or explicit ID
732#[derive(Debug, Clone, Serialize, Deserialize)]
733pub struct AssignStrategy {
734    pub strategy: String,
735    pub capabilities: Option<Vec<String>>,
736    pub seniority: Option<String>,
737    pub role: Option<String>,
738    pub agent_id: Option<String>,
739}
740
741// --- Capability Targeting ---
742
743#[derive(Debug, Clone)]
744pub struct CapabilityTarget {
745    pub target_type: String,
746    pub target_id: String,
747}
748
749// ===== Task Questions =====
750
751#[derive(Debug, Clone, Serialize, Deserialize)]
752pub struct TaskQuestion {
753    pub id: String,
754    pub task_id: String,
755    pub question: String,
756    pub question_type: String,
757    pub context: Option<String>,
758    pub asked_by_type: String,
759    pub asked_by_id: String,
760    pub target_type: Option<String>,
761    pub target_id: Option<String>,
762    pub required_capability: Option<String>,
763    pub status: String,
764    pub blocking: bool,
765    pub resolved_by_type: Option<String>,
766    pub resolved_by_id: Option<String>,
767    pub resolution: Option<String>,
768    pub created_at: String,
769    pub resolved_at: Option<String>,
770}
771
772#[derive(Debug, Deserialize)]
773pub struct CreateQuestion {
774    pub question: String,
775    pub question_type: Option<String>,
776    pub context: Option<String>,
777    pub target_type: Option<String>,
778    pub target_id: Option<String>,
779    pub required_capability: Option<String>,
780    pub blocking: Option<bool>,
781}
782
783#[derive(Debug, Deserialize)]
784pub struct ResolveQuestion {
785    pub resolution: String,
786}
787
788#[derive(Debug, Deserialize)]
789pub struct QuestionQuery {
790    pub status: Option<String>,
791    pub unrouted: Option<bool>,
792}
793
794#[derive(Debug, Clone, Serialize, Deserialize)]
795pub struct QuestionReply {
796    pub id: String,
797    pub question_id: String,
798    pub author_type: String,
799    pub author_id: String,
800    pub body: String,
801    pub is_resolution: bool,
802    pub created_at: String,
803}
804
805#[derive(Debug, Deserialize)]
806pub struct CreateReply {
807    pub body: String,
808    pub is_resolution: Option<bool>,
809}
810
811#[derive(Debug, Deserialize)]
812pub struct DismissQuestion {
813    pub reason: String,
814}
815
816#[derive(Debug, Deserialize)]
817pub struct AssignQuestion {
818    pub target_type: String,
819    pub target_id: String,
820}
821
822// ===== Agent Inbox =====
823
824#[derive(Debug, Serialize)]
825pub struct InboxItem {
826    pub id: String,
827    pub item_type: String,
828    pub title: String,
829    pub status: Option<String>,
830    pub priority: Option<String>,
831    pub action: String,
832    pub action_hint: String,
833    pub project_id: Option<String>,
834    pub tags: Vec<String>,
835    pub updated_at: Option<String>,
836    pub metadata: Option<serde_json::Value>,
837}
838
839#[derive(Debug, Serialize)]
840pub struct InboxCapacity {
841    pub max_concurrent_tasks: i64,
842    pub current_active_tasks: i64,
843    pub has_capacity: bool,
844}
845
846#[derive(Debug, Serialize)]
847pub struct AgentInbox {
848    pub summary: String,
849    pub todo_tasks: Vec<InboxItem>,
850    pub in_progress_tasks: Vec<InboxItem>,
851    pub review_tasks: Vec<InboxItem>,
852    pub blocked_tasks: Vec<InboxItem>,
853    pub handoff_tasks: Vec<InboxItem>,
854    pub open_questions: Vec<InboxItem>,
855    pub unread_notifications: Vec<InboxItem>,
856    pub capacity: InboxCapacity,
857}
858
859// ===== Inbound Webhook Triggers =====
860
861#[derive(Debug, Serialize, Deserialize, Clone)]
862pub struct WebhookTrigger {
863    pub id: String,
864    pub project_id: String,
865    pub name: String,
866    pub action_type: String,
867    pub action_config: serde_json::Value,
868    pub enabled: bool,
869    #[serde(default)]
870    pub ip_allowlist: Vec<String>,
871    pub created_at: String,
872    pub updated_at: String,
873}
874
875/// Returned only on creation (raw secret is not stored)
876#[derive(Debug, Serialize)]
877pub struct TriggerCreatedResponse {
878    pub trigger: WebhookTrigger,
879    pub secret: String,
880}
881
882#[derive(Debug, Deserialize)]
883pub struct CreateTriggerRequest {
884    pub name: String,
885    pub action_type: String,
886    pub action_config: serde_json::Value,
887}
888
889#[derive(Debug, Deserialize)]
890pub struct UpdateTriggerRequest {
891    pub name: Option<String>,
892    pub action_type: Option<String>,
893    pub action_config: Option<serde_json::Value>,
894    pub enabled: Option<bool>,
895    pub ip_allowlist: Option<Vec<String>>,
896}
897
898#[derive(Debug, Serialize, Deserialize)]
899pub struct WebhookTriggerLog {
900    pub id: String,
901    pub trigger_id: String,
902    pub received_at: String,
903    pub status: String,
904    pub payload: Option<serde_json::Value>,
905    pub result: Option<serde_json::Value>,
906    pub error: Option<String>,
907}