feat(sagent): add P0 sandbox-agent tables
Add the agents / agent / agent_message / agent_event_line / agent_watch entities plus their lookup indexes and the event-line XOR guard.
ZhenYi committed
Added lib/models/src/sagent/agent.rs lib/models/src/sagent/agent.rs +122 −0 View lib/models/src/sagent/agent.rs
@@ -0,0 +1,122 @@
1use sea_orm::entity::prelude::*;
2use serde::{Deserialize, Serialize};
3use time::OffsetDateTime;
4use uuid::Uuid;
5
6#[derive(
7 Debug,
8 Clone,
9 Copy,
10 PartialEq,
11 Eq,
12 Default,
13 EnumIter,
14 DeriveActiveEnum,
15 Serialize,
16 Deserialize,
17 utoipa::ToSchema,
18)]
19#[serde(rename_all = "snake_case")]
20#[sea_orm(rs_type = "i16", db_type = "SmallInteger")]
21pub enum AgentState {
22 #[default]
23 #[sea_orm(num_value = 0)]
24 Draft,
25 #[sea_orm(num_value = 1)]
26 Pending,
27 #[sea_orm(num_value = 2)]
28 Planning,
29 #[sea_orm(num_value = 3)]
30 Executing,
31 #[sea_orm(num_value = 4)]
32 Waiting,
33 #[sea_orm(num_value = 5)]
34 Succeeded,
35 #[sea_orm(num_value = 6)]
36 Failed,
37 #[sea_orm(num_value = 7)]
38 Canceled,
39 #[sea_orm(num_value = 8)]
40 Closed,
41}
42
43impl AgentState {
44 pub fn is_terminal(self) -> bool {
45 matches!(
46 self,
47 Self::Succeeded | Self::Failed | Self::Canceled | Self::Closed
48 )
49 }
50
51 pub fn is_active(self) -> bool {
52 matches!(
53 self,
54 Self::Pending | Self::Planning | Self::Executing | Self::Waiting
55 )
56 }
57}
58
59#[derive(
60 Debug,
61 Clone,
62 Copy,
63 PartialEq,
64 Eq,
65 Default,
66 EnumIter,
67 DeriveActiveEnum,
68 Serialize,
69 Deserialize,
70 utoipa::ToSchema,
71)]
72#[serde(rename_all = "snake_case")]
73#[sea_orm(rs_type = "i16", db_type = "SmallInteger")]
74pub enum AgentPriority {
75 #[default]
76 #[sea_orm(num_value = 0)]
77 None,
78 #[sea_orm(num_value = 1)]
79 Low,
80 #[sea_orm(num_value = 2)]
81 Medium,
82 #[sea_orm(num_value = 3)]
83 High,
84 #[sea_orm(num_value = 4)]
85 Urgent,
86}
87
88#[sea_orm::model]
89#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
90#[sea_orm(table_name = "agent")]
91pub struct Model {
92 #[sea_orm(primary_key, auto_increment = false)]
93 pub id: Uuid,
94 #[sea_orm(unique_key = "collection_number")]
95 pub collection_id: Uuid,
96 #[sea_orm(unique_key = "collection_number")]
97 pub autoincrement_id: i32,
98 pub author_id: Uuid,
99 pub title: String,
100 pub description: Option<String>,
101 pub body: String,
102 #[sea_orm(default_value = 0)]
103 pub state: AgentState,
104 #[sea_orm(default_value = 0)]
105 pub priority: AgentPriority,
106 pub due_at: Option<OffsetDateTime>,
107 pub session_id: Option<Uuid>,
108 pub last_run_id: Option<Uuid>,
109 #[sea_orm(default_value = 0)]
110 pub last_event_seq: i64,
111 pub created_at: OffsetDateTime,
112 pub updated_at: Option<OffsetDateTime>,
113 pub started_at: Option<OffsetDateTime>,
114 pub finished_at: Option<OffsetDateTime>,
115 pub closed_at: Option<OffsetDateTime>,
116 #[sea_orm(belongs_to, from = "collection_id", to = "id")]
117 pub collection: BelongsTo<super::agents::Entity>,
118 #[sea_orm(belongs_to, from = "author_id", to = "uid")]
119 pub author: BelongsTo<super::super::users::user::Entity>,
120}
121
122impl ActiveModelBehavior for ActiveModel {}
Added lib/models/src/sagent/agent_event_line.rs lib/models/src/sagent/agent_event_line.rs +78 −0 View lib/models/src/sagent/agent_event_line.rs
@@ -0,0 +1,78 @@
1use sea_orm::entity::prelude::*;
2use serde::{Deserialize, Serialize};
3use time::OffsetDateTime;
4use uuid::Uuid;
5
6#[derive(
7 Debug,
8 Clone,
9 Copy,
10 PartialEq,
11 Eq,
12 EnumIter,
13 DeriveActiveEnum,
14 Serialize,
15 Deserialize,
16 utoipa::ToSchema,
17)]
18#[serde(rename_all = "snake_case")]
19#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(32))")]
20pub enum AgentEventType {
21 #[sea_orm(string_value = "created")]
22 Created,
23 #[sea_orm(string_value = "title_updated")]
24 TitleUpdated,
25 #[sea_orm(string_value = "body_updated")]
26 BodyUpdated,
27 #[sea_orm(string_value = "started")]
28 Started,
29 #[sea_orm(string_value = "state_changed")]
30 StateChanged,
31 #[sea_orm(string_value = "message_added")]
32 MessageAdded,
33 #[sea_orm(string_value = "run_failed")]
34 RunFailed,
35 #[sea_orm(string_value = "artifact_added")]
36 ArtifactAdded,
37 #[sea_orm(string_value = "assigned")]
38 Assigned,
39 #[sea_orm(string_value = "unassigned")]
40 Unassigned,
41 #[sea_orm(string_value = "label_added")]
42 LabelAdded,
43 #[sea_orm(string_value = "label_removed")]
44 LabelRemoved,
45 #[sea_orm(string_value = "repo_relation_added")]
46 RepoRelationAdded,
47 #[sea_orm(string_value = "repo_relation_removed")]
48 RepoRelationRemoved,
49 #[sea_orm(string_value = "subscriber_lost")]
50 SubscriberLost,
51 #[sea_orm(string_value = "closed")]
52 Closed,
53 #[sea_orm(string_value = "reopened")]
54 Reopened,
55}
56
57#[sea_orm::model]
58#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
59#[sea_orm(table_name = "agent_event_line")]
60pub struct Model {
61 #[sea_orm(primary_key, auto_increment = false)]
62 pub id: Uuid,
63 pub agent_id: Option<Uuid>,
64 pub collection_id: Option<Uuid>,
65 pub user_id: Option<Uuid>,
66 pub event_type: AgentEventType,
67 pub run_id: Option<Uuid>,
68 pub content: Option<Json>,
69 pub created_at: OffsetDateTime,
70 #[sea_orm(belongs_to, from = "agent_id", to = "id")]
71 pub agent: BelongsTo<Option<super::agent::Entity>>,
72 #[sea_orm(belongs_to, from = "collection_id", to = "id")]
73 pub collection: BelongsTo<Option<super::agents::Entity>>,
74 #[sea_orm(belongs_to, from = "user_id", to = "uid")]
75 pub user: BelongsTo<Option<super::super::users::user::Entity>>,
76}
77
78impl ActiveModelBehavior for ActiveModel {}
Added lib/models/src/sagent/agent_message.rs lib/models/src/sagent/agent_message.rs +55 −0 View lib/models/src/sagent/agent_message.rs
@@ -0,0 +1,55 @@
1use sea_orm::entity::prelude::*;
2use serde::{Deserialize, Serialize};
3use time::OffsetDateTime;
4use uuid::Uuid;
5
6#[derive(
7 Debug,
8 Clone,
9 Copy,
10 PartialEq,
11 Eq,
12 Default,
13 EnumIter,
14 DeriveActiveEnum,
15 Serialize,
16 Deserialize,
17 utoipa::ToSchema,
18)]
19#[serde(rename_all = "snake_case")]
20#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(16))")]
21pub enum AgentRole {
22 #[default]
23 #[sea_orm(string_value = "user")]
24 User,
25 #[sea_orm(string_value = "agent")]
26 Agent,
27 #[sea_orm(string_value = "system")]
28 System,
29 #[sea_orm(string_value = "tool")]
30 Tool,
31}
32
33#[sea_orm::model]
34#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
35#[sea_orm(table_name = "agent_message")]
36pub struct Model {
37 #[sea_orm(primary_key, auto_increment = false)]
38 pub id: Uuid,
39 #[sea_orm(unique_key = "agent_seq")]
40 pub agent_id: Uuid,
41 #[sea_orm(unique_key = "agent_seq")]
42 pub seq: i64,
43 pub gateway_message_id: Option<Uuid>,
44 pub author_id: Option<Uuid>,
45 pub role: AgentRole,
46 pub content: String,
47 pub created_at: OffsetDateTime,
48 pub updated_at: Option<OffsetDateTime>,
49 #[sea_orm(belongs_to, from = "agent_id", to = "id")]
50 pub agent: BelongsTo<super::agent::Entity>,
51 #[sea_orm(belongs_to, from = "author_id", to = "uid")]
52 pub author: BelongsTo<Option<super::super::users::user::Entity>>,
53}
54
55impl ActiveModelBehavior for ActiveModel {}
Added lib/models/src/sagent/agent_watch.rs lib/models/src/sagent/agent_watch.rs +25 −0 View lib/models/src/sagent/agent_watch.rs
@@ -0,0 +1,25 @@
1use crate::common::watch_mode::WatchMode;
2use sea_orm::entity::prelude::*;
3use time::OffsetDateTime;
4use uuid::Uuid;
5
6#[sea_orm::model]
7#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
8#[sea_orm(table_name = "agent_watch")]
9pub struct Model {
10 #[sea_orm(primary_key, auto_increment = false)]
11 pub id: Uuid,
12 #[sea_orm(unique_key = "agent_user")]
13 pub agent_id: Uuid,
14 #[sea_orm(unique_key = "agent_user")]
15 pub user_id: Uuid,
16 pub mode: WatchMode,
17 pub created_at: OffsetDateTime,
18 pub updated_at: Option<OffsetDateTime>,
19 #[sea_orm(belongs_to, from = "agent_id", to = "id")]
20 pub agent: BelongsTo<super::agent::Entity>,
21 #[sea_orm(belongs_to, from = "user_id", to = "uid")]
22 pub user: BelongsTo<super::super::users::user::Entity>,
23}
24
25impl ActiveModelBehavior for ActiveModel {}
No newline at end of file
Added lib/models/src/sagent/agents.rs lib/models/src/sagent/agents.rs +22 −0 View lib/models/src/sagent/agents.rs
@@ -0,0 +1,22 @@
1use sea_orm::entity::prelude::*;
2use time::OffsetDateTime;
3use uuid::Uuid;
4
5#[sea_orm::model]
6#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
7#[sea_orm(table_name = "agents")]
8pub struct Model {
9 #[sea_orm(primary_key, auto_increment = false)]
10 pub id: Uuid,
11 pub dir_id: Uuid,
12 pub name: String,
13 pub icon: Option<String>,
14 pub description: Option<String>,
15 pub config: Option<Json>,
16 pub created_at: OffsetDateTime,
17 pub updated_at: Option<OffsetDateTime>,
18 #[sea_orm(belongs_to, from = "dir_id", to = "id")]
19 pub dir: BelongsTo<super::super::dir::dirs::Entity>,
20}
21
22impl ActiveModelBehavior for ActiveModel {}
Added lib/models/src/sagent/mod.rs lib/models/src/sagent/mod.rs +7 −0 View lib/models/src/sagent/mod.rs
@@ -0,0 +1,7 @@
1#![allow(clippy::module_inception)]
2
3pub mod agent;
4pub mod agent_event_line;
5pub mod agent_message;
6pub mod agent_watch;
7pub mod agents;
Modified lib/models/src/lib.rs lib/models/src/lib.rs +1 −0 View lib/models/src/lib.rs
@@ -12,6 +12,7 @@
1212pub mod pull_request;
1313pub mod repo;
1414pub mod room;
15pub mod sagent;
1516pub mod search;
1617pub mod space;
1718pub mod system;
Modified lib/models/schema_extras.sql lib/models/schema_extras.sql +27 −0 View lib/models/schema_extras.sql
@@ -299,3 +299,30 @@
299299
300300CREATE INDEX IF NOT EXISTS idx_ci_runner_state
301301 ON ci_runner (state, last_seen_at DESC);
302
303-- ---------------------------------------------------------------------------
304-- Sagent domain (lib/sagent): XOR guard for polymorphic event lines, plus
305-- lookup indexes.
306--
307-- Caveat: SeaORM sync drops any unique index on a managed table whose column
308-- list does not match an entity-declared unique key, and it does so with
309-- ALTER TABLE ... DROP CONSTRAINT, which fails on plain CREATE UNIQUE INDEX.
310-- So only NON-unique indexes (or partial variants of a declared unique key)
311-- may be added here.
312-- ---------------------------------------------------------------------------
313DO $$ BEGIN
314 IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'agent_event_target_xor') THEN
315 ALTER TABLE agent_event_line
316 ADD CONSTRAINT agent_event_target_xor
317 CHECK (((agent_id IS NOT NULL)::int + (collection_id IS NOT NULL)::int) = 1);
318 END IF;
319END $$;
320
321CREATE INDEX IF NOT EXISTS idx_agent_collection_state
322 ON agent (collection_id, state);
323CREATE INDEX IF NOT EXISTS idx_agent_active
324 ON agent (state) WHERE state IN (1, 2, 3, 4);
325CREATE INDEX IF NOT EXISTS idx_agent_message_agent_created
326 ON agent_message (agent_id, created_at DESC);
327CREATE INDEX IF NOT EXISTS idx_agent_event_line_agent_created
328 ON agent_event_line (agent_id, created_at DESC);
Modified .gitignore .gitignore +1 −0 View .gitignore
@@ -10,3 +10,4 @@
1010admin/dist
1111admin/node_modules
1212.codegraph
13sagent-plan.md