diff --git a/coderd/database/check_constraint.go b/coderd/database/check_constraint.go index b61650c68a..6ba18bd6c6 100644 --- a/coderd/database/check_constraint.go +++ b/coderd/database/check_constraint.go @@ -21,13 +21,13 @@ const ( CheckChatUsageLimitConfigSingletonCheck CheckConstraint = "chat_usage_limit_config_singleton_check" // chat_usage_limit_config CheckChatsPinOrderArchivedCheck CheckConstraint = "chats_pin_order_archived_check" // chats CheckChatsPinOrderParentCheck CheckConstraint = "chats_pin_order_parent_check" // chats - CheckOrganizationIDNotZero CheckConstraint = "organization_id_not_zero" // custom_roles - CheckGroupsChatSpendLimitMicrosCheck CheckConstraint = "groups_chat_spend_limit_micros_check" // groups CheckOneTimePasscodeSet CheckConstraint = "one_time_passcode_set" // users CheckUsersChatSpendLimitMicrosCheck CheckConstraint = "users_chat_spend_limit_micros_check" // users CheckUsersEmailNotEmpty CheckConstraint = "users_email_not_empty" // users CheckUsersServiceAccountLoginType CheckConstraint = "users_service_account_login_type" // users CheckUsersUsernameMinLength CheckConstraint = "users_username_min_length" // users + CheckOrganizationIDNotZero CheckConstraint = "organization_id_not_zero" // custom_roles + CheckGroupsChatSpendLimitMicrosCheck CheckConstraint = "groups_chat_spend_limit_micros_check" // groups CheckMcpServerConfigsAuthTypeCheck CheckConstraint = "mcp_server_configs_auth_type_check" // mcp_server_configs CheckMcpServerConfigsAvailabilityCheck CheckConstraint = "mcp_server_configs_availability_check" // mcp_server_configs CheckMcpServerConfigsTransportCheck CheckConstraint = "mcp_server_configs_transport_check" // mcp_server_configs diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index e5968cb437..792f3712af 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1555,6 +1555,91 @@ CREATE TABLE chats ( CONSTRAINT chats_pin_order_parent_check CHECK (((pin_order = 0) OR (parent_chat_id IS NULL))) ); +CREATE TABLE users ( + id uuid NOT NULL, + email text NOT NULL, + username text DEFAULT ''::text NOT NULL, + hashed_password bytea NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + status user_status DEFAULT 'dormant'::user_status NOT NULL, + rbac_roles text[] DEFAULT '{}'::text[] NOT NULL, + login_type login_type DEFAULT 'password'::login_type NOT NULL, + avatar_url text DEFAULT ''::text NOT NULL, + deleted boolean DEFAULT false NOT NULL, + last_seen_at timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL, + quiet_hours_schedule text DEFAULT ''::text NOT NULL, + name text DEFAULT ''::text NOT NULL, + github_com_user_id bigint, + hashed_one_time_passcode bytea, + one_time_passcode_expires_at timestamp with time zone, + is_system boolean DEFAULT false NOT NULL, + is_service_account boolean DEFAULT false NOT NULL, + chat_spend_limit_micros bigint, + CONSTRAINT one_time_passcode_set CHECK ((((hashed_one_time_passcode IS NULL) AND (one_time_passcode_expires_at IS NULL)) OR ((hashed_one_time_passcode IS NOT NULL) AND (one_time_passcode_expires_at IS NOT NULL)))), + CONSTRAINT users_chat_spend_limit_micros_check CHECK (((chat_spend_limit_micros IS NULL) OR (chat_spend_limit_micros > 0))), + CONSTRAINT users_email_not_empty CHECK (((is_service_account = true) = (email = ''::text))), + CONSTRAINT users_service_account_login_type CHECK (((is_service_account = false) OR (login_type = 'none'::login_type))), + CONSTRAINT users_username_min_length CHECK ((length(username) >= 1)) +); + +COMMENT ON COLUMN users.quiet_hours_schedule IS 'Daily (!) cron schedule (with optional CRON_TZ) signifying the start of the user''s quiet hours. If empty, the default quiet hours on the instance is used instead.'; + +COMMENT ON COLUMN users.name IS 'Name of the Coder user'; + +COMMENT ON COLUMN users.github_com_user_id IS 'The GitHub.com numerical user ID. It is used to check if the user has starred the Coder repository. It is also used for filtering users in the users list CLI command, and may become more widely used in the future.'; + +COMMENT ON COLUMN users.hashed_one_time_passcode IS 'A hash of the one-time-passcode given to the user.'; + +COMMENT ON COLUMN users.one_time_passcode_expires_at IS 'The time when the one-time-passcode expires.'; + +COMMENT ON COLUMN users.is_system IS 'Determines if a user is a system user, and therefore cannot login or perform normal actions'; + +COMMENT ON COLUMN users.is_service_account IS 'Determines if a user is an admin-managed account that cannot login'; + +CREATE VIEW visible_users AS + SELECT users.id, + users.username, + users.name, + users.avatar_url + FROM users; + +COMMENT ON VIEW visible_users IS 'Visible fields of users are allowed to be joined with other tables for including context of other resources.'; + +CREATE VIEW chats_expanded AS + SELECT c.id, + c.owner_id, + c.workspace_id, + c.title, + c.status, + c.worker_id, + c.started_at, + c.heartbeat_at, + c.created_at, + c.updated_at, + c.parent_chat_id, + c.root_chat_id, + c.last_model_config_id, + c.archived, + c.last_error, + c.mode, + c.mcp_server_ids, + c.labels, + c.build_id, + c.agent_id, + c.pin_order, + c.last_read_message_id, + c.last_injected_context, + c.dynamic_tools, + c.organization_id, + c.plan_mode, + c.client_type, + c.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM (chats c + JOIN visible_users owner ON ((owner.id = c.owner_id))); + CREATE TABLE connection_logs ( id uuid NOT NULL, connect_time timestamp with time zone NOT NULL, @@ -1709,48 +1794,6 @@ CREATE TABLE organization_members ( roles text[] DEFAULT '{}'::text[] NOT NULL ); -CREATE TABLE users ( - id uuid NOT NULL, - email text NOT NULL, - username text DEFAULT ''::text NOT NULL, - hashed_password bytea NOT NULL, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, - status user_status DEFAULT 'dormant'::user_status NOT NULL, - rbac_roles text[] DEFAULT '{}'::text[] NOT NULL, - login_type login_type DEFAULT 'password'::login_type NOT NULL, - avatar_url text DEFAULT ''::text NOT NULL, - deleted boolean DEFAULT false NOT NULL, - last_seen_at timestamp without time zone DEFAULT '0001-01-01 00:00:00'::timestamp without time zone NOT NULL, - quiet_hours_schedule text DEFAULT ''::text NOT NULL, - name text DEFAULT ''::text NOT NULL, - github_com_user_id bigint, - hashed_one_time_passcode bytea, - one_time_passcode_expires_at timestamp with time zone, - is_system boolean DEFAULT false NOT NULL, - is_service_account boolean DEFAULT false NOT NULL, - chat_spend_limit_micros bigint, - CONSTRAINT one_time_passcode_set CHECK ((((hashed_one_time_passcode IS NULL) AND (one_time_passcode_expires_at IS NULL)) OR ((hashed_one_time_passcode IS NOT NULL) AND (one_time_passcode_expires_at IS NOT NULL)))), - CONSTRAINT users_chat_spend_limit_micros_check CHECK (((chat_spend_limit_micros IS NULL) OR (chat_spend_limit_micros > 0))), - CONSTRAINT users_email_not_empty CHECK (((is_service_account = true) = (email = ''::text))), - CONSTRAINT users_service_account_login_type CHECK (((is_service_account = false) OR (login_type = 'none'::login_type))), - CONSTRAINT users_username_min_length CHECK ((length(username) >= 1)) -); - -COMMENT ON COLUMN users.quiet_hours_schedule IS 'Daily (!) cron schedule (with optional CRON_TZ) signifying the start of the user''s quiet hours. If empty, the default quiet hours on the instance is used instead.'; - -COMMENT ON COLUMN users.name IS 'Name of the Coder user'; - -COMMENT ON COLUMN users.github_com_user_id IS 'The GitHub.com numerical user ID. It is used to check if the user has starred the Coder repository. It is also used for filtering users in the users list CLI command, and may become more widely used in the future.'; - -COMMENT ON COLUMN users.hashed_one_time_passcode IS 'A hash of the one-time-passcode given to the user.'; - -COMMENT ON COLUMN users.one_time_passcode_expires_at IS 'The time when the one-time-passcode expires.'; - -COMMENT ON COLUMN users.is_system IS 'Determines if a user is a system user, and therefore cannot login or perform normal actions'; - -COMMENT ON COLUMN users.is_service_account IS 'Determines if a user is an admin-managed account that cannot login'; - CREATE VIEW group_members_expanded AS WITH all_members AS ( SELECT group_members.user_id, @@ -2301,15 +2344,6 @@ CREATE TABLE tasks ( COMMENT ON COLUMN tasks.display_name IS 'Display name is a custom, human-friendly task name.'; -CREATE VIEW visible_users AS - SELECT users.id, - users.username, - users.name, - users.avatar_url - FROM users; - -COMMENT ON VIEW visible_users IS 'Visible fields of users are allowed to be joined with other tables for including context of other resources.'; - CREATE TABLE workspace_agents ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, diff --git a/coderd/database/gentest/models_test.go b/coderd/database/gentest/models_test.go index cf27671a2c..071deaa13b 100644 --- a/coderd/database/gentest/models_test.go +++ b/coderd/database/gentest/models_test.go @@ -98,6 +98,19 @@ func TestViewSubsetWorkspace(t *testing.T) { } } +func TestViewSubsetChat(t *testing.T) { + t.Parallel() + table := reflect.TypeOf(database.ChatTable{}) + joined := reflect.TypeOf(database.Chat{}) + + tableFields := allFields(table) + joinedFields := allFields(joined) + if !assert.Subset(t, fieldNames(joinedFields), fieldNames(tableFields), "table is not subset") { + t.Log("Some fields were added to the Chat Table without updating the 'chats_expanded' view.") + t.Log("See migration 000496_chat_database_foundation.up.sql to create the view.") + } +} + func fieldNames(fields []reflect.StructField) []string { names := make([]string, len(fields)) for i, field := range fields { diff --git a/coderd/database/migrations/000496_chat_database_foundation.down.sql b/coderd/database/migrations/000496_chat_database_foundation.down.sql new file mode 100644 index 0000000000..1cf600a62e --- /dev/null +++ b/coderd/database/migrations/000496_chat_database_foundation.down.sql @@ -0,0 +1 @@ +DROP VIEW IF EXISTS chats_expanded; diff --git a/coderd/database/migrations/000496_chat_database_foundation.up.sql b/coderd/database/migrations/000496_chat_database_foundation.up.sql new file mode 100644 index 0000000000..fda55e86e9 --- /dev/null +++ b/coderd/database/migrations/000496_chat_database_foundation.up.sql @@ -0,0 +1,35 @@ +CREATE VIEW chats_expanded AS +SELECT + c.id, + c.owner_id, + c.workspace_id, + c.title, + c.status, + c.worker_id, + c.started_at, + c.heartbeat_at, + c.created_at, + c.updated_at, + c.parent_chat_id, + c.root_chat_id, + c.last_model_config_id, + c.archived, + c.last_error, + c.mode, + c.mcp_server_ids, + c.labels, + c.build_id, + c.agent_id, + c.pin_order, + c.last_read_message_id, + c.last_injected_context, + c.dynamic_tools, + c.organization_id, + c.plan_mode, + c.client_type, + c.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name +FROM + chats c + JOIN visible_users owner ON owner.id = c.owner_id; diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index dcacc7ac9b..408ac7a43b 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -804,6 +804,8 @@ func (q *sqlQuerier) GetAuthorizedChats(ctx context.Context, arg GetChatsParams, &i.Chat.PlanMode, &i.Chat.ClientType, &i.Chat.LastTurnSummary, + &i.Chat.OwnerUsername, + &i.Chat.OwnerName, &i.HasUnread); err != nil { return nil, err } diff --git a/coderd/database/models.go b/coderd/database/models.go index bf3e2233df..296c8232bc 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -4512,6 +4512,8 @@ type Chat struct { PlanMode NullChatPlanMode `db:"plan_mode" json:"plan_mode"` ClientType ChatClientType `db:"client_type" json:"client_type"` LastTurnSummary sql.NullString `db:"last_turn_summary" json:"last_turn_summary"` + OwnerUsername string `db:"owner_username" json:"owner_username"` + OwnerName string `db:"owner_name" json:"owner_name"` } type ChatDebugRun struct { @@ -4660,6 +4662,37 @@ type ChatQueuedMessage struct { ModelConfigID uuid.NullUUID `db:"model_config_id" json:"model_config_id"` } +type ChatTable struct { + ID uuid.UUID `db:"id" json:"id"` + OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` + WorkspaceID uuid.NullUUID `db:"workspace_id" json:"workspace_id"` + Title string `db:"title" json:"title"` + Status ChatStatus `db:"status" json:"status"` + WorkerID uuid.NullUUID `db:"worker_id" json:"worker_id"` + StartedAt sql.NullTime `db:"started_at" json:"started_at"` + HeartbeatAt sql.NullTime `db:"heartbeat_at" json:"heartbeat_at"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + ParentChatID uuid.NullUUID `db:"parent_chat_id" json:"parent_chat_id"` + RootChatID uuid.NullUUID `db:"root_chat_id" json:"root_chat_id"` + LastModelConfigID uuid.UUID `db:"last_model_config_id" json:"last_model_config_id"` + Archived bool `db:"archived" json:"archived"` + LastError pqtype.NullRawMessage `db:"last_error" json:"last_error"` + Mode NullChatMode `db:"mode" json:"mode"` + MCPServerIDs []uuid.UUID `db:"mcp_server_ids" json:"mcp_server_ids"` + Labels StringMap `db:"labels" json:"labels"` + BuildID uuid.NullUUID `db:"build_id" json:"build_id"` + AgentID uuid.NullUUID `db:"agent_id" json:"agent_id"` + PinOrder int32 `db:"pin_order" json:"pin_order"` + LastReadMessageID sql.NullInt64 `db:"last_read_message_id" json:"last_read_message_id"` + LastInjectedContext pqtype.NullRawMessage `db:"last_injected_context" json:"last_injected_context"` + DynamicTools pqtype.NullRawMessage `db:"dynamic_tools" json:"dynamic_tools"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + PlanMode NullChatPlanMode `db:"plan_mode" json:"plan_mode"` + ClientType ChatClientType `db:"client_type" json:"client_type"` + LastTurnSummary sql.NullString `db:"last_turn_summary" json:"last_turn_summary"` +} + type ChatUsageLimitConfig struct { ID int64 `db:"id" json:"id"` Singleton bool `db:"singleton" json:"singleton"` diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index cb9f9d0920..305ec47940 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -11740,11 +11740,15 @@ func TestChatLabels(t *testing.T) { }) require.NoError(t, err) require.Equal(t, database.StringMap{"github.repo": "coder/coder", "env": "prod"}, chat.Labels) + require.Equal(t, owner.Username, chat.OwnerUsername) + require.Equal(t, owner.Name, chat.OwnerName) // Read back and verify. fetched, err := db.GetChatByID(ctx, chat.ID) require.NoError(t, err) require.Equal(t, chat.Labels, fetched.Labels) + require.Equal(t, owner.Username, fetched.OwnerUsername) + require.Equal(t, owner.Name, fetched.OwnerName) }) t.Run("CreateWithoutLabels", func(t *testing.T) { @@ -11765,6 +11769,66 @@ func TestChatLabels(t *testing.T) { require.Empty(t, chat.Labels) }) + t.Run("ListReturnsOwnerFields", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + + chat, err := db.InsertChat(ctx, database.InsertChatParams{ + OrganizationID: org.ID, + Status: database.ChatStatusWaiting, + ClientType: database.ChatClientTypeUi, + OwnerID: owner.ID, + LastModelConfigID: modelCfg.ID, + Title: "owner-fields-chat-" + uuid.NewString(), + }) + require.NoError(t, err) + + rows, err := db.GetChats(ctx, database.GetChatsParams{OwnerID: owner.ID}) + require.NoError(t, err) + + chatIndex := slices.IndexFunc(rows, func(row database.GetChatsRow) bool { + return row.Chat.ID == chat.ID + }) + require.NotEqual(t, -1, chatIndex, "chat not found in GetChats result") + require.Equal(t, owner.Username, rows[chatIndex].Chat.OwnerUsername) + require.Equal(t, owner.Name, rows[chatIndex].Chat.OwnerName) + }) + + t.Run("ChildrenReturnOwnerFields", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + + parent, err := db.InsertChat(ctx, database.InsertChatParams{ + OrganizationID: org.ID, + Status: database.ChatStatusWaiting, + ClientType: database.ChatClientTypeUi, + OwnerID: owner.ID, + LastModelConfigID: modelCfg.ID, + Title: "owner-fields-parent-" + uuid.NewString(), + }) + require.NoError(t, err) + child, err := db.InsertChat(ctx, database.InsertChatParams{ + OrganizationID: org.ID, + Status: database.ChatStatusWaiting, + ClientType: database.ChatClientTypeUi, + OwnerID: owner.ID, + LastModelConfigID: modelCfg.ID, + Title: "owner-fields-child-" + uuid.NewString(), + ParentChatID: uuid.NullUUID{UUID: parent.ID, Valid: true}, + RootChatID: uuid.NullUUID{UUID: parent.ID, Valid: true}, + }) + require.NoError(t, err) + + rows, err := db.GetChildChatsByParentIDs(ctx, database.GetChildChatsByParentIDsParams{ + ParentIds: []uuid.UUID{parent.ID}, + }) + require.NoError(t, err) + require.Len(t, rows, 1) + require.Equal(t, child.ID, rows[0].Chat.ID) + require.Equal(t, owner.Username, rows[0].Chat.OwnerUsername) + require.Equal(t, owner.Name, rows[0].Chat.OwnerName) + }) + t.Run("UpdateLabels", func(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitMedium) @@ -11834,6 +11898,8 @@ func TestChatLabels(t *testing.T) { require.NoError(t, err) require.Equal(t, "new-title", updated.Title) require.Equal(t, database.StringMap{"pr": "1234"}, updated.Labels) + require.Equal(t, owner.Username, updated.OwnerUsername) + require.Equal(t, owner.Name, updated.OwnerName) }) t.Run("FilterByLabels", func(t *testing.T) { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index dd5a186970..513105f5cd 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5643,6 +5643,7 @@ func (q *sqlQuerier) UpdateChatProvider(ctx context.Context, arg UpdateChatProvi } const acquireChats = `-- name: AcquireChats :many +WITH acquired_chats AS ( UPDATE chats SET @@ -5667,8 +5668,46 @@ WHERE LIMIT $3::int ) -RETURNING - id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +RETURNING id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +), +chats_expanded AS ( + SELECT + acquired_chats.id, + acquired_chats.owner_id, + acquired_chats.workspace_id, + acquired_chats.title, + acquired_chats.status, + acquired_chats.worker_id, + acquired_chats.started_at, + acquired_chats.heartbeat_at, + acquired_chats.created_at, + acquired_chats.updated_at, + acquired_chats.parent_chat_id, + acquired_chats.root_chat_id, + acquired_chats.last_model_config_id, + acquired_chats.archived, + acquired_chats.last_error, + acquired_chats.mode, + acquired_chats.mcp_server_ids, + acquired_chats.labels, + acquired_chats.build_id, + acquired_chats.agent_id, + acquired_chats.pin_order, + acquired_chats.last_read_message_id, + acquired_chats.last_injected_context, + acquired_chats.dynamic_tools, + acquired_chats.organization_id, + acquired_chats.plan_mode, + acquired_chats.client_type, + acquired_chats.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + acquired_chats + JOIN visible_users owner ON owner.id = acquired_chats.owner_id +) +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded ` type AcquireChatsParams struct { @@ -5717,6 +5756,8 @@ func (q *sqlQuerier) AcquireChats(ctx context.Context, arg AcquireChatsParams) ( &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ); err != nil { return nil, err } @@ -5851,15 +5892,51 @@ func (q *sqlQuerier) AcquireStaleChatDiffStatuses(ctx context.Context, limitVal } const archiveChatByID = `-- name: ArchiveChatByID :many -WITH chats AS ( +WITH updated_chats AS ( UPDATE chats SET archived = true, pin_order = 0, updated_at = NOW() WHERE id = $1::uuid OR root_chat_id = $1::uuid RETURNING id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +), +chats_expanded AS ( + SELECT + updated_chats.id, + updated_chats.owner_id, + updated_chats.workspace_id, + updated_chats.title, + updated_chats.status, + updated_chats.worker_id, + updated_chats.started_at, + updated_chats.heartbeat_at, + updated_chats.created_at, + updated_chats.updated_at, + updated_chats.parent_chat_id, + updated_chats.root_chat_id, + updated_chats.last_model_config_id, + updated_chats.archived, + updated_chats.last_error, + updated_chats.mode, + updated_chats.mcp_server_ids, + updated_chats.labels, + updated_chats.build_id, + updated_chats.agent_id, + updated_chats.pin_order, + updated_chats.last_read_message_id, + updated_chats.last_injected_context, + updated_chats.dynamic_tools, + updated_chats.organization_id, + updated_chats.plan_mode, + updated_chats.client_type, + updated_chats.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chats + JOIN visible_users owner ON owner.id = updated_chats.owner_id ) -SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary -FROM chats -ORDER BY (id = $1::uuid) DESC, created_at ASC, id ASC +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded +ORDER BY (chats_expanded.id = $1::uuid) DESC, chats_expanded.created_at ASC, chats_expanded.id ASC ` func (q *sqlQuerier) ArchiveChatByID(ctx context.Context, id uuid.UUID) ([]Chat, error) { @@ -5900,6 +5977,8 @@ func (q *sqlQuerier) ArchiveChatByID(ctx context.Context, id uuid.UUID) ([]Chat, &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ); err != nil { return nil, err } @@ -6194,8 +6273,8 @@ func (q *sqlQuerier) DeleteOldChats(ctx context.Context, arg DeleteOldChatsParam } const getActiveChatsByAgentID = `-- name: GetActiveChatsByAgentID :many -SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary -FROM chats +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded WHERE agent_id = $1::uuid AND archived = false -- Active statuses only: waiting, pending, running, paused, @@ -6243,6 +6322,8 @@ func (q *sqlQuerier) GetActiveChatsByAgentID(ctx context.Context, agentID uuid.U &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ); err != nil { return nil, err } @@ -6258,12 +6339,9 @@ func (q *sqlQuerier) GetActiveChatsByAgentID(ctx context.Context, agentID uuid.U } const getChatByID = `-- name: GetChatByID :one -SELECT - id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary -FROM - chats -WHERE - id = $1::uuid +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded +WHERE id = $1::uuid ` func (q *sqlQuerier) GetChatByID(ctx context.Context, id uuid.UUID) (Chat, error) { @@ -6298,12 +6376,56 @@ func (q *sqlQuerier) GetChatByID(ctx context.Context, id uuid.UUID) (Chat, error &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ) return i, err } const getChatByIDForUpdate = `-- name: GetChatByIDForUpdate :one -SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary FROM chats WHERE id = $1::uuid FOR UPDATE +WITH locked_chat AS ( + SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary + FROM chats + WHERE id = $1::uuid + FOR UPDATE +), +chats_expanded AS ( + SELECT + locked_chat.id, + locked_chat.owner_id, + locked_chat.workspace_id, + locked_chat.title, + locked_chat.status, + locked_chat.worker_id, + locked_chat.started_at, + locked_chat.heartbeat_at, + locked_chat.created_at, + locked_chat.updated_at, + locked_chat.parent_chat_id, + locked_chat.root_chat_id, + locked_chat.last_model_config_id, + locked_chat.archived, + locked_chat.last_error, + locked_chat.mode, + locked_chat.mcp_server_ids, + locked_chat.labels, + locked_chat.build_id, + locked_chat.agent_id, + locked_chat.pin_order, + locked_chat.last_read_message_id, + locked_chat.last_injected_context, + locked_chat.dynamic_tools, + locked_chat.organization_id, + locked_chat.plan_mode, + locked_chat.client_type, + locked_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM locked_chat + JOIN visible_users owner ON owner.id = locked_chat.owner_id +) +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded ` func (q *sqlQuerier) GetChatByIDForUpdate(ctx context.Context, id uuid.UUID) (Chat, error) { @@ -6338,6 +6460,8 @@ func (q *sqlQuerier) GetChatByIDForUpdate(ctx context.Context, id uuid.UUID) (Ch &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ) return i, err } @@ -7510,25 +7634,33 @@ func (q *sqlQuerier) GetChatUserPromptsByChatID(ctx context.Context, arg GetChat } const getChats = `-- name: GetChats :many +WITH cursor_chat AS ( + SELECT + pin_order, + updated_at, + id + FROM chats + WHERE id = $3 +) SELECT - chats.id, chats.owner_id, chats.workspace_id, chats.title, chats.status, chats.worker_id, chats.started_at, chats.heartbeat_at, chats.created_at, chats.updated_at, chats.parent_chat_id, chats.root_chat_id, chats.last_model_config_id, chats.archived, chats.last_error, chats.mode, chats.mcp_server_ids, chats.labels, chats.build_id, chats.agent_id, chats.pin_order, chats.last_read_message_id, chats.last_injected_context, chats.dynamic_tools, chats.organization_id, chats.plan_mode, chats.client_type, chats.last_turn_summary, + chats_expanded.id, chats_expanded.owner_id, chats_expanded.workspace_id, chats_expanded.title, chats_expanded.status, chats_expanded.worker_id, chats_expanded.started_at, chats_expanded.heartbeat_at, chats_expanded.created_at, chats_expanded.updated_at, chats_expanded.parent_chat_id, chats_expanded.root_chat_id, chats_expanded.last_model_config_id, chats_expanded.archived, chats_expanded.last_error, chats_expanded.mode, chats_expanded.mcp_server_ids, chats_expanded.labels, chats_expanded.build_id, chats_expanded.agent_id, chats_expanded.pin_order, chats_expanded.last_read_message_id, chats_expanded.last_injected_context, chats_expanded.dynamic_tools, chats_expanded.organization_id, chats_expanded.plan_mode, chats_expanded.client_type, chats_expanded.last_turn_summary, chats_expanded.owner_username, chats_expanded.owner_name, EXISTS ( SELECT 1 FROM chat_messages cm - WHERE cm.chat_id = chats.id + WHERE cm.chat_id = chats_expanded.id AND cm.role = 'assistant' AND cm.deleted = false - AND cm.id > COALESCE(chats.last_read_message_id, 0) + AND cm.id > COALESCE(chats_expanded.last_read_message_id, 0) ) AS has_unread FROM - chats + chats_expanded WHERE CASE - WHEN $1 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN chats.owner_id = $1 + WHEN $1 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN chats_expanded.owner_id = $1 ELSE true END AND CASE WHEN $2 :: boolean IS NULL THEN true - ELSE chats.archived = $2 :: boolean + ELSE chats_expanded.archived = $2 :: boolean END AND CASE -- Cursor pagination: the last element on a page acts as the cursor. @@ -7536,19 +7668,20 @@ WHERE -- (pin_order is negated so lower values sort first in DESC order), -- which lets us use a single tuple < comparison. WHEN $3 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN ( - (CASE WHEN pin_order > 0 THEN 1 ELSE 0 END, -pin_order, updated_at, id) < ( + (CASE WHEN chats_expanded.pin_order > 0 THEN 1 ELSE 0 END, -chats_expanded.pin_order, chats_expanded.updated_at, chats_expanded.id) < ( SELECT - CASE WHEN c2.pin_order > 0 THEN 1 ELSE 0 END, -c2.pin_order, c2.updated_at, c2.id + CASE WHEN cursor_chat.pin_order > 0 THEN 1 ELSE 0 END, + -cursor_chat.pin_order, + cursor_chat.updated_at, + cursor_chat.id FROM - chats c2 - WHERE - c2.id = $3 + cursor_chat ) ) ELSE true END AND CASE - WHEN $4::jsonb IS NOT NULL THEN chats.labels @> $4::jsonb + WHEN $4::jsonb IS NOT NULL THEN chats_expanded.labels @> $4::jsonb ELSE true END -- Match chats whose linked diff URL (e.g. a pull request URL) @@ -7563,7 +7696,7 @@ WHERE WHERE cds.url IS NOT NULL AND cds.url <> '' AND LOWER(cds.url) = LOWER($5::text) - AND (c2.id = chats.id OR c2.root_chat_id = chats.id) + AND (c2.id = chats_expanded.id OR c2.root_chat_id = chats_expanded.id) ) ELSE true END @@ -7571,7 +7704,7 @@ WHERE -- separately via GetChildChatsByParentIDs and embedded under -- each parent. Other callers that need the full set should -- use a narrower query (e.g. GetChatsByWorkspaceIDs). - AND chats.parent_chat_id IS NULL + AND chats_expanded.parent_chat_id IS NULL -- Authorize Filter clause will be injected below in GetAuthorizedChats -- @authorize_filter ORDER BY @@ -7579,10 +7712,10 @@ ORDER BY -- pinned chats, lower pin_order values come first. The negation -- trick (-pin_order) keeps all sort columns DESC so the cursor -- tuple < comparison works with uniform direction. - CASE WHEN pin_order > 0 THEN 1 ELSE 0 END DESC, - -pin_order DESC, - updated_at DESC, - id DESC + CASE WHEN chats_expanded.pin_order > 0 THEN 1 ELSE 0 END DESC, + -chats_expanded.pin_order DESC, + chats_expanded.updated_at DESC, + chats_expanded.id DESC OFFSET $6 LIMIT -- The chat list is unbounded and expected to grow large. @@ -7651,6 +7784,8 @@ func (q *sqlQuerier) GetChats(ctx context.Context, arg GetChatsParams) ([]GetCha &i.Chat.PlanMode, &i.Chat.ClientType, &i.Chat.LastTurnSummary, + &i.Chat.OwnerUsername, + &i.Chat.OwnerName, &i.HasUnread, ); err != nil { return nil, err @@ -7667,8 +7802,8 @@ func (q *sqlQuerier) GetChats(ctx context.Context, arg GetChatsParams) ([]GetCha } const getChatsByWorkspaceIDs = `-- name: GetChatsByWorkspaceIDs :many -SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary -FROM chats +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded WHERE archived = false AND workspace_id = ANY($1::uuid[]) ORDER BY workspace_id, updated_at DESC @@ -7712,6 +7847,8 @@ func (q *sqlQuerier) GetChatsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ); err != nil { return nil, err } @@ -7796,25 +7933,25 @@ func (q *sqlQuerier) GetChatsUpdatedAfter(ctx context.Context, updatedAfter time const getChildChatsByParentIDs = `-- name: GetChildChatsByParentIDs :many SELECT - chats.id, chats.owner_id, chats.workspace_id, chats.title, chats.status, chats.worker_id, chats.started_at, chats.heartbeat_at, chats.created_at, chats.updated_at, chats.parent_chat_id, chats.root_chat_id, chats.last_model_config_id, chats.archived, chats.last_error, chats.mode, chats.mcp_server_ids, chats.labels, chats.build_id, chats.agent_id, chats.pin_order, chats.last_read_message_id, chats.last_injected_context, chats.dynamic_tools, chats.organization_id, chats.plan_mode, chats.client_type, chats.last_turn_summary, + chats_expanded.id, chats_expanded.owner_id, chats_expanded.workspace_id, chats_expanded.title, chats_expanded.status, chats_expanded.worker_id, chats_expanded.started_at, chats_expanded.heartbeat_at, chats_expanded.created_at, chats_expanded.updated_at, chats_expanded.parent_chat_id, chats_expanded.root_chat_id, chats_expanded.last_model_config_id, chats_expanded.archived, chats_expanded.last_error, chats_expanded.mode, chats_expanded.mcp_server_ids, chats_expanded.labels, chats_expanded.build_id, chats_expanded.agent_id, chats_expanded.pin_order, chats_expanded.last_read_message_id, chats_expanded.last_injected_context, chats_expanded.dynamic_tools, chats_expanded.organization_id, chats_expanded.plan_mode, chats_expanded.client_type, chats_expanded.last_turn_summary, chats_expanded.owner_username, chats_expanded.owner_name, EXISTS ( SELECT 1 FROM chat_messages cm - WHERE cm.chat_id = chats.id + WHERE cm.chat_id = chats_expanded.id AND cm.role = 'assistant' AND cm.deleted = false - AND cm.id > COALESCE(chats.last_read_message_id, 0) + AND cm.id > COALESCE(chats_expanded.last_read_message_id, 0) ) AS has_unread FROM - chats + chats_expanded WHERE - chats.parent_chat_id = ANY($1 :: uuid[]) + chats_expanded.parent_chat_id = ANY($1 :: uuid[]) AND CASE WHEN $2 :: boolean IS NULL THEN true - ELSE chats.archived = $2 :: boolean + ELSE chats_expanded.archived = $2 :: boolean END ORDER BY - chats.created_at DESC, - chats.id DESC + chats_expanded.created_at DESC, + chats_expanded.id DESC ` type GetChildChatsByParentIDsParams struct { @@ -7869,6 +8006,8 @@ func (q *sqlQuerier) GetChildChatsByParentIDs(ctx context.Context, arg GetChildC &i.Chat.PlanMode, &i.Chat.ClientType, &i.Chat.LastTurnSummary, + &i.Chat.OwnerUsername, + &i.Chat.OwnerName, &i.HasUnread, ); err != nil { return nil, err @@ -7935,9 +8074,9 @@ func (q *sqlQuerier) GetLastChatMessageByRole(ctx context.Context, arg GetLastCh const getStaleChats = `-- name: GetStaleChats :many SELECT - id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary + id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name FROM - chats + chats_expanded WHERE (status = 'running'::chat_status AND heartbeat_at < $1::timestamptz) @@ -7947,7 +8086,7 @@ WHERE AND updated_at < $1::timestamptz AND EXISTS ( SELECT 1 FROM chat_queued_messages cqm - WHERE cqm.chat_id = chats.id + WHERE cqm.chat_id = chats_expanded.id )) ` @@ -7996,6 +8135,8 @@ func (q *sqlQuerier) GetStaleChats(ctx context.Context, staleThreshold time.Time &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ); err != nil { return nil, err } @@ -8073,6 +8214,7 @@ func (q *sqlQuerier) GetUserGroupSpendLimit(ctx context.Context, arg GetUserGrou } const insertChat = `-- name: InsertChat :one +WITH inserted_chat AS ( INSERT INTO chats ( organization_id, owner_id, @@ -8108,8 +8250,46 @@ INSERT INTO chats ( $15::jsonb, $16::chat_client_type ) -RETURNING - id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +RETURNING id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +), +chats_expanded AS ( + SELECT + inserted_chat.id, + inserted_chat.owner_id, + inserted_chat.workspace_id, + inserted_chat.title, + inserted_chat.status, + inserted_chat.worker_id, + inserted_chat.started_at, + inserted_chat.heartbeat_at, + inserted_chat.created_at, + inserted_chat.updated_at, + inserted_chat.parent_chat_id, + inserted_chat.root_chat_id, + inserted_chat.last_model_config_id, + inserted_chat.archived, + inserted_chat.last_error, + inserted_chat.mode, + inserted_chat.mcp_server_ids, + inserted_chat.labels, + inserted_chat.build_id, + inserted_chat.agent_id, + inserted_chat.pin_order, + inserted_chat.last_read_message_id, + inserted_chat.last_injected_context, + inserted_chat.dynamic_tools, + inserted_chat.organization_id, + inserted_chat.plan_mode, + inserted_chat.client_type, + inserted_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + inserted_chat + JOIN visible_users owner ON owner.id = inserted_chat.owner_id +) +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded ` type InsertChatParams struct { @@ -8180,6 +8360,8 @@ func (q *sqlQuerier) InsertChat(ctx context.Context, arg InsertChatParams) (Chat &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ) return i, err } @@ -8735,16 +8917,52 @@ func (q *sqlQuerier) SoftDeleteContextFileMessages(ctx context.Context, chatID u } const unarchiveChatByID = `-- name: UnarchiveChatByID :many -WITH chats AS ( +WITH updated_chats AS ( UPDATE chats SET archived = false, updated_at = NOW() WHERE id = $1::uuid OR root_chat_id = $1::uuid RETURNING id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +), +chats_expanded AS ( + SELECT + updated_chats.id, + updated_chats.owner_id, + updated_chats.workspace_id, + updated_chats.title, + updated_chats.status, + updated_chats.worker_id, + updated_chats.started_at, + updated_chats.heartbeat_at, + updated_chats.created_at, + updated_chats.updated_at, + updated_chats.parent_chat_id, + updated_chats.root_chat_id, + updated_chats.last_model_config_id, + updated_chats.archived, + updated_chats.last_error, + updated_chats.mode, + updated_chats.mcp_server_ids, + updated_chats.labels, + updated_chats.build_id, + updated_chats.agent_id, + updated_chats.pin_order, + updated_chats.last_read_message_id, + updated_chats.last_injected_context, + updated_chats.dynamic_tools, + updated_chats.organization_id, + updated_chats.plan_mode, + updated_chats.client_type, + updated_chats.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chats + JOIN visible_users owner ON owner.id = updated_chats.owner_id ) -SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary -FROM chats -ORDER BY (id = $1::uuid) DESC, created_at ASC, id ASC +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded +ORDER BY (chats_expanded.id = $1::uuid) DESC, chats_expanded.created_at ASC, chats_expanded.id ASC ` // Unarchives a chat (and its children). Stale file references are @@ -8789,6 +9007,8 @@ func (q *sqlQuerier) UnarchiveChatByID(ctx context.Context, id uuid.UUID) ([]Cha &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ); err != nil { return nil, err } @@ -8863,6 +9083,7 @@ func (q *sqlQuerier) UnpinChatByID(ctx context.Context, id uuid.UUID) error { } const updateChatBuildAgentBinding = `-- name: UpdateChatBuildAgentBinding :one +WITH updated_chat AS ( UPDATE chats SET build_id = $1::uuid, agent_id = $2::uuid, @@ -8870,6 +9091,45 @@ UPDATE chats SET WHERE id = $3::uuid RETURNING id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded ` type UpdateChatBuildAgentBindingParams struct { @@ -8910,11 +9170,14 @@ func (q *sqlQuerier) UpdateChatBuildAgentBinding(ctx context.Context, arg Update &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ) return i, err } const updateChatByID = `-- name: UpdateChatByID :one +WITH updated_chat AS ( UPDATE chats SET @@ -8922,8 +9185,46 @@ SET updated_at = NOW() WHERE id = $2::uuid -RETURNING - id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +RETURNING id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded ` type UpdateChatByIDParams struct { @@ -8963,6 +9264,8 @@ func (q *sqlQuerier) UpdateChatByID(ctx context.Context, arg UpdateChatByIDParam &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ) return i, err } @@ -9013,6 +9316,7 @@ func (q *sqlQuerier) UpdateChatHeartbeats(ctx context.Context, arg UpdateChatHea } const updateChatLabelsByID = `-- name: UpdateChatLabelsByID :one +WITH updated_chat AS ( UPDATE chats SET @@ -9020,8 +9324,46 @@ SET updated_at = NOW() WHERE id = $2::uuid -RETURNING - id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +RETURNING id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded ` type UpdateChatLabelsByIDParams struct { @@ -9061,16 +9403,58 @@ func (q *sqlQuerier) UpdateChatLabelsByID(ctx context.Context, arg UpdateChatLab &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ) return i, err } const updateChatLastInjectedContext = `-- name: UpdateChatLastInjectedContext :one +WITH updated_chat AS ( UPDATE chats SET last_injected_context = $1::jsonb WHERE id = $2::uuid RETURNING id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded ` type UpdateChatLastInjectedContextParams struct { @@ -9114,11 +9498,14 @@ func (q *sqlQuerier) UpdateChatLastInjectedContext(ctx context.Context, arg Upda &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ) return i, err } const updateChatLastModelConfigByID = `-- name: UpdateChatLastModelConfigByID :one +WITH updated_chat AS ( UPDATE chats SET @@ -9126,8 +9513,46 @@ SET last_model_config_id = $1::uuid WHERE id = $2::uuid -RETURNING - id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +RETURNING id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded ` type UpdateChatLastModelConfigByIDParams struct { @@ -9167,6 +9592,8 @@ func (q *sqlQuerier) UpdateChatLastModelConfigByID(ctx context.Context, arg Upda &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ) return i, err } @@ -9224,6 +9651,7 @@ func (q *sqlQuerier) UpdateChatLastTurnSummary(ctx context.Context, arg UpdateCh } const updateChatMCPServerIDs = `-- name: UpdateChatMCPServerIDs :one +WITH updated_chat AS ( UPDATE chats SET @@ -9231,8 +9659,46 @@ SET updated_at = NOW() WHERE id = $2::uuid -RETURNING - id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +RETURNING id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded ` type UpdateChatMCPServerIDsParams struct { @@ -9272,6 +9738,8 @@ func (q *sqlQuerier) UpdateChatMCPServerIDs(ctx context.Context, arg UpdateChatM &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ) return i, err } @@ -9395,6 +9863,7 @@ func (q *sqlQuerier) UpdateChatPinOrder(ctx context.Context, arg UpdateChatPinOr } const updateChatPlanModeByID = `-- name: UpdateChatPlanModeByID :one +WITH updated_chat AS ( UPDATE chats SET @@ -9402,8 +9871,46 @@ SET plan_mode = $1::chat_plan_mode WHERE id = $2::uuid -RETURNING - id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +RETURNING id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded ` type UpdateChatPlanModeByIDParams struct { @@ -9443,11 +9950,14 @@ func (q *sqlQuerier) UpdateChatPlanModeByID(ctx context.Context, arg UpdateChatP &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ) return i, err } const updateChatStatus = `-- name: UpdateChatStatus :one +WITH updated_chat AS ( UPDATE chats SET @@ -9459,8 +9969,46 @@ SET updated_at = NOW() WHERE id = $6::uuid -RETURNING - id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +RETURNING id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded ` type UpdateChatStatusParams struct { @@ -9511,11 +10059,14 @@ func (q *sqlQuerier) UpdateChatStatus(ctx context.Context, arg UpdateChatStatusP &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ) return i, err } const updateChatStatusPreserveUpdatedAt = `-- name: UpdateChatStatusPreserveUpdatedAt :one +WITH updated_chat AS ( UPDATE chats SET @@ -9527,8 +10078,46 @@ SET updated_at = $6::timestamptz WHERE id = $7::uuid -RETURNING - id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +RETURNING id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded ` type UpdateChatStatusPreserveUpdatedAtParams struct { @@ -9581,11 +10170,14 @@ func (q *sqlQuerier) UpdateChatStatusPreserveUpdatedAt(ctx context.Context, arg &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ) return i, err } const updateChatTitleByID = `-- name: UpdateChatTitleByID :one +WITH updated_chat AS ( UPDATE chats SET @@ -9595,8 +10187,46 @@ SET title = $1::text WHERE id = $2::uuid -RETURNING - id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +RETURNING id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded ` type UpdateChatTitleByIDParams struct { @@ -9636,11 +10266,14 @@ func (q *sqlQuerier) UpdateChatTitleByID(ctx context.Context, arg UpdateChatTitl &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ) return i, err } const updateChatWorkspaceBinding = `-- name: UpdateChatWorkspaceBinding :one +WITH updated_chat AS ( UPDATE chats SET workspace_id = $1::uuid, build_id = $2::uuid, @@ -9648,6 +10281,45 @@ UPDATE chats SET updated_at = NOW() WHERE id = $4::uuid RETURNING id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT id, owner_id, workspace_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error, mode, mcp_server_ids, labels, build_id, agent_id, pin_order, last_read_message_id, last_injected_context, dynamic_tools, organization_id, plan_mode, client_type, last_turn_summary, owner_username, owner_name +FROM chats_expanded ` type UpdateChatWorkspaceBindingParams struct { @@ -9694,6 +10366,8 @@ func (q *sqlQuerier) UpdateChatWorkspaceBinding(ctx context.Context, arg UpdateC &i.PlanMode, &i.ClientType, &i.LastTurnSummary, + &i.OwnerUsername, + &i.OwnerName, ) return i, err } diff --git a/coderd/database/queries/chats.sql b/coderd/database/queries/chats.sql index 5666887076..c327cc6a4f 100644 --- a/coderd/database/queries/chats.sql +++ b/coderd/database/queries/chats.sql @@ -1,29 +1,101 @@ -- name: ArchiveChatByID :many -WITH chats AS ( +WITH updated_chats AS ( UPDATE chats SET archived = true, pin_order = 0, updated_at = NOW() WHERE id = @id::uuid OR root_chat_id = @id::uuid RETURNING * +), +chats_expanded AS ( + SELECT + updated_chats.id, + updated_chats.owner_id, + updated_chats.workspace_id, + updated_chats.title, + updated_chats.status, + updated_chats.worker_id, + updated_chats.started_at, + updated_chats.heartbeat_at, + updated_chats.created_at, + updated_chats.updated_at, + updated_chats.parent_chat_id, + updated_chats.root_chat_id, + updated_chats.last_model_config_id, + updated_chats.archived, + updated_chats.last_error, + updated_chats.mode, + updated_chats.mcp_server_ids, + updated_chats.labels, + updated_chats.build_id, + updated_chats.agent_id, + updated_chats.pin_order, + updated_chats.last_read_message_id, + updated_chats.last_injected_context, + updated_chats.dynamic_tools, + updated_chats.organization_id, + updated_chats.plan_mode, + updated_chats.client_type, + updated_chats.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chats + JOIN visible_users owner ON owner.id = updated_chats.owner_id ) SELECT * -FROM chats -ORDER BY (id = @id::uuid) DESC, created_at ASC, id ASC; +FROM chats_expanded +ORDER BY (chats_expanded.id = @id::uuid) DESC, chats_expanded.created_at ASC, chats_expanded.id ASC; -- name: UnarchiveChatByID :many -- Unarchives a chat (and its children). Stale file references are -- handled automatically by FK cascades on chat_file_links: when -- dbpurge deletes a chat_files row, the corresponding -- chat_file_links rows are cascade-deleted by PostgreSQL. -WITH chats AS ( +WITH updated_chats AS ( UPDATE chats SET archived = false, updated_at = NOW() WHERE id = @id::uuid OR root_chat_id = @id::uuid RETURNING * +), +chats_expanded AS ( + SELECT + updated_chats.id, + updated_chats.owner_id, + updated_chats.workspace_id, + updated_chats.title, + updated_chats.status, + updated_chats.worker_id, + updated_chats.started_at, + updated_chats.heartbeat_at, + updated_chats.created_at, + updated_chats.updated_at, + updated_chats.parent_chat_id, + updated_chats.root_chat_id, + updated_chats.last_model_config_id, + updated_chats.archived, + updated_chats.last_error, + updated_chats.mode, + updated_chats.mcp_server_ids, + updated_chats.labels, + updated_chats.build_id, + updated_chats.agent_id, + updated_chats.pin_order, + updated_chats.last_read_message_id, + updated_chats.last_injected_context, + updated_chats.dynamic_tools, + updated_chats.organization_id, + updated_chats.plan_mode, + updated_chats.client_type, + updated_chats.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chats + JOIN visible_users owner ON owner.id = updated_chats.owner_id ) SELECT * -FROM chats -ORDER BY (id = @id::uuid) DESC, created_at ASC, id ASC; +FROM chats_expanded +ORDER BY (chats_expanded.id = @id::uuid) DESC, chats_expanded.created_at ASC, chats_expanded.id ASC; -- name: PinChatByID :exec WITH target_chat AS ( @@ -211,12 +283,9 @@ WHERE id = @id::bigint; -- name: GetChatByID :one -SELECT - * -FROM - chats -WHERE - id = @id::uuid; +SELECT * +FROM chats_expanded +WHERE id = @id::uuid; -- name: GetChatMessageByID :one SELECT @@ -368,25 +437,33 @@ ORDER BY id ASC; -- name: GetChats :many +WITH cursor_chat AS ( + SELECT + pin_order, + updated_at, + id + FROM chats + WHERE id = @after_id +) SELECT - sqlc.embed(chats), + sqlc.embed(chats_expanded), EXISTS ( SELECT 1 FROM chat_messages cm - WHERE cm.chat_id = chats.id + WHERE cm.chat_id = chats_expanded.id AND cm.role = 'assistant' AND cm.deleted = false - AND cm.id > COALESCE(chats.last_read_message_id, 0) + AND cm.id > COALESCE(chats_expanded.last_read_message_id, 0) ) AS has_unread FROM - chats + chats_expanded WHERE CASE - WHEN @owner_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN chats.owner_id = @owner_id + WHEN @owner_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN chats_expanded.owner_id = @owner_id ELSE true END AND CASE WHEN sqlc.narg('archived') :: boolean IS NULL THEN true - ELSE chats.archived = sqlc.narg('archived') :: boolean + ELSE chats_expanded.archived = sqlc.narg('archived') :: boolean END AND CASE -- Cursor pagination: the last element on a page acts as the cursor. @@ -394,19 +471,20 @@ WHERE -- (pin_order is negated so lower values sort first in DESC order), -- which lets us use a single tuple < comparison. WHEN @after_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN ( - (CASE WHEN pin_order > 0 THEN 1 ELSE 0 END, -pin_order, updated_at, id) < ( + (CASE WHEN chats_expanded.pin_order > 0 THEN 1 ELSE 0 END, -chats_expanded.pin_order, chats_expanded.updated_at, chats_expanded.id) < ( SELECT - CASE WHEN c2.pin_order > 0 THEN 1 ELSE 0 END, -c2.pin_order, c2.updated_at, c2.id + CASE WHEN cursor_chat.pin_order > 0 THEN 1 ELSE 0 END, + -cursor_chat.pin_order, + cursor_chat.updated_at, + cursor_chat.id FROM - chats c2 - WHERE - c2.id = @after_id + cursor_chat ) ) ELSE true END AND CASE - WHEN sqlc.narg('label_filter')::jsonb IS NOT NULL THEN chats.labels @> sqlc.narg('label_filter')::jsonb + WHEN sqlc.narg('label_filter')::jsonb IS NOT NULL THEN chats_expanded.labels @> sqlc.narg('label_filter')::jsonb ELSE true END -- Match chats whose linked diff URL (e.g. a pull request URL) @@ -421,7 +499,7 @@ WHERE WHERE cds.url IS NOT NULL AND cds.url <> '' AND LOWER(cds.url) = LOWER(sqlc.narg('diff_url')::text) - AND (c2.id = chats.id OR c2.root_chat_id = chats.id) + AND (c2.id = chats_expanded.id OR c2.root_chat_id = chats_expanded.id) ) ELSE true END @@ -429,7 +507,7 @@ WHERE -- separately via GetChildChatsByParentIDs and embedded under -- each parent. Other callers that need the full set should -- use a narrower query (e.g. GetChatsByWorkspaceIDs). - AND chats.parent_chat_id IS NULL + AND chats_expanded.parent_chat_id IS NULL -- Authorize Filter clause will be injected below in GetAuthorizedChats -- @authorize_filter ORDER BY @@ -437,10 +515,10 @@ ORDER BY -- pinned chats, lower pin_order values come first. The negation -- trick (-pin_order) keeps all sort columns DESC so the cursor -- tuple < comparison works with uniform direction. - CASE WHEN pin_order > 0 THEN 1 ELSE 0 END DESC, - -pin_order DESC, - updated_at DESC, - id DESC + CASE WHEN chats_expanded.pin_order > 0 THEN 1 ELSE 0 END DESC, + -chats_expanded.pin_order DESC, + chats_expanded.updated_at DESC, + chats_expanded.id DESC OFFSET @offset_opt LIMIT -- The chat list is unbounded and expected to grow large. @@ -453,27 +531,28 @@ LIMIT -- invariant (parent archived implies child archived) is enforced -- at write time, not here. SELECT - sqlc.embed(chats), + sqlc.embed(chats_expanded), EXISTS ( SELECT 1 FROM chat_messages cm - WHERE cm.chat_id = chats.id + WHERE cm.chat_id = chats_expanded.id AND cm.role = 'assistant' AND cm.deleted = false - AND cm.id > COALESCE(chats.last_read_message_id, 0) + AND cm.id > COALESCE(chats_expanded.last_read_message_id, 0) ) AS has_unread FROM - chats + chats_expanded WHERE - chats.parent_chat_id = ANY(@parent_ids :: uuid[]) + chats_expanded.parent_chat_id = ANY(@parent_ids :: uuid[]) AND CASE WHEN sqlc.narg('archived') :: boolean IS NULL THEN true - ELSE chats.archived = sqlc.narg('archived') :: boolean + ELSE chats_expanded.archived = sqlc.narg('archived') :: boolean END ORDER BY - chats.created_at DESC, - chats.id DESC; + chats_expanded.created_at DESC, + chats_expanded.id DESC; -- name: InsertChat :one +WITH inserted_chat AS ( INSERT INTO chats ( organization_id, owner_id, @@ -509,8 +588,46 @@ INSERT INTO chats ( sqlc.narg('dynamic_tools')::jsonb, @client_type::chat_client_type ) -RETURNING - *; +RETURNING * +), +chats_expanded AS ( + SELECT + inserted_chat.id, + inserted_chat.owner_id, + inserted_chat.workspace_id, + inserted_chat.title, + inserted_chat.status, + inserted_chat.worker_id, + inserted_chat.started_at, + inserted_chat.heartbeat_at, + inserted_chat.created_at, + inserted_chat.updated_at, + inserted_chat.parent_chat_id, + inserted_chat.root_chat_id, + inserted_chat.last_model_config_id, + inserted_chat.archived, + inserted_chat.last_error, + inserted_chat.mode, + inserted_chat.mcp_server_ids, + inserted_chat.labels, + inserted_chat.build_id, + inserted_chat.agent_id, + inserted_chat.pin_order, + inserted_chat.last_read_message_id, + inserted_chat.last_injected_context, + inserted_chat.dynamic_tools, + inserted_chat.organization_id, + inserted_chat.plan_mode, + inserted_chat.client_type, + inserted_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + inserted_chat + JOIN visible_users owner ON owner.id = inserted_chat.owner_id +) +SELECT * +FROM chats_expanded; -- name: InsertChatMessages :many WITH updated_chat AS ( @@ -595,6 +712,7 @@ RETURNING *; -- name: UpdateChatByID :one +WITH updated_chat AS ( UPDATE chats SET @@ -602,10 +720,49 @@ SET updated_at = NOW() WHERE id = @id::uuid -RETURNING - *; +RETURNING * +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT * +FROM chats_expanded; -- name: UpdateChatTitleByID :one +WITH updated_chat AS ( UPDATE chats SET @@ -615,10 +772,49 @@ SET title = @title::text WHERE id = @id::uuid -RETURNING - *; +RETURNING * +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT * +FROM chats_expanded; -- name: UpdateChatPlanModeByID :one +WITH updated_chat AS ( UPDATE chats SET @@ -626,10 +822,49 @@ SET plan_mode = sqlc.narg('plan_mode')::chat_plan_mode WHERE id = @id::uuid -RETURNING - *; +RETURNING * +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT * +FROM chats_expanded; -- name: UpdateChatLastModelConfigByID :one +WITH updated_chat AS ( UPDATE chats SET @@ -637,10 +872,49 @@ SET last_model_config_id = @last_model_config_id::uuid WHERE id = @id::uuid -RETURNING - *; +RETURNING * +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT * +FROM chats_expanded; -- name: UpdateChatLabelsByID :one +WITH updated_chat AS ( UPDATE chats SET @@ -648,28 +922,147 @@ SET updated_at = NOW() WHERE id = @id::uuid -RETURNING - *; +RETURNING * +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT * +FROM chats_expanded; -- name: UpdateChatWorkspaceBinding :one +WITH updated_chat AS ( UPDATE chats SET workspace_id = sqlc.narg('workspace_id')::uuid, build_id = sqlc.narg('build_id')::uuid, agent_id = sqlc.narg('agent_id')::uuid, updated_at = NOW() WHERE id = @id::uuid -RETURNING *; +RETURNING * +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT * +FROM chats_expanded; -- name: UpdateChatBuildAgentBinding :one +WITH updated_chat AS ( UPDATE chats SET build_id = sqlc.narg('build_id')::uuid, agent_id = sqlc.narg('agent_id')::uuid, updated_at = NOW() WHERE id = @id::uuid -RETURNING *; +RETURNING * +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT * +FROM chats_expanded; -- name: UpdateChatLastInjectedContext :one +WITH updated_chat AS ( -- Updates the cached injected context parts (AGENTS.md + -- skills) on the chat row. Called only when context changes -- (first workspace attach or agent change). updated_at is @@ -678,7 +1071,46 @@ UPDATE chats SET last_injected_context = sqlc.narg('last_injected_context')::jsonb WHERE id = @id::uuid -RETURNING *; +RETURNING * +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT * +FROM chats_expanded; -- name: UpdateChatLastTurnSummary :execrows -- Updates the cached last completed turn summary for sidebar display. @@ -700,6 +1132,7 @@ WHERE AND updated_at = @expected_updated_at::timestamptz; -- name: UpdateChatMCPServerIDs :one +WITH updated_chat AS ( UPDATE chats SET @@ -707,8 +1140,46 @@ SET updated_at = NOW() WHERE id = @id::uuid -RETURNING - *; +RETURNING * +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT * +FROM chats_expanded; -- name: LinkChatFiles :one -- LinkChatFiles inserts file associations into the chat_file_links @@ -750,6 +1221,7 @@ SELECT -- name: AcquireChats :many -- Acquires up to @num_chats pending chats for processing. Uses SKIP LOCKED -- to prevent multiple replicas from acquiring the same chat. +WITH acquired_chats AS ( UPDATE chats SET @@ -774,10 +1246,49 @@ WHERE LIMIT @num_chats::int ) -RETURNING - *; +RETURNING * +), +chats_expanded AS ( + SELECT + acquired_chats.id, + acquired_chats.owner_id, + acquired_chats.workspace_id, + acquired_chats.title, + acquired_chats.status, + acquired_chats.worker_id, + acquired_chats.started_at, + acquired_chats.heartbeat_at, + acquired_chats.created_at, + acquired_chats.updated_at, + acquired_chats.parent_chat_id, + acquired_chats.root_chat_id, + acquired_chats.last_model_config_id, + acquired_chats.archived, + acquired_chats.last_error, + acquired_chats.mode, + acquired_chats.mcp_server_ids, + acquired_chats.labels, + acquired_chats.build_id, + acquired_chats.agent_id, + acquired_chats.pin_order, + acquired_chats.last_read_message_id, + acquired_chats.last_injected_context, + acquired_chats.dynamic_tools, + acquired_chats.organization_id, + acquired_chats.plan_mode, + acquired_chats.client_type, + acquired_chats.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + acquired_chats + JOIN visible_users owner ON owner.id = acquired_chats.owner_id +) +SELECT * +FROM chats_expanded; -- name: UpdateChatStatus :one +WITH updated_chat AS ( UPDATE chats SET @@ -789,10 +1300,49 @@ SET updated_at = NOW() WHERE id = @id::uuid -RETURNING - *; +RETURNING * +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT * +FROM chats_expanded; -- name: UpdateChatStatusPreserveUpdatedAt :one +WITH updated_chat AS ( UPDATE chats SET @@ -804,8 +1354,46 @@ SET updated_at = @updated_at::timestamptz WHERE id = @id::uuid -RETURNING - *; +RETURNING * +), +chats_expanded AS ( + SELECT + updated_chat.id, + updated_chat.owner_id, + updated_chat.workspace_id, + updated_chat.title, + updated_chat.status, + updated_chat.worker_id, + updated_chat.started_at, + updated_chat.heartbeat_at, + updated_chat.created_at, + updated_chat.updated_at, + updated_chat.parent_chat_id, + updated_chat.root_chat_id, + updated_chat.last_model_config_id, + updated_chat.archived, + updated_chat.last_error, + updated_chat.mode, + updated_chat.mcp_server_ids, + updated_chat.labels, + updated_chat.build_id, + updated_chat.agent_id, + updated_chat.pin_order, + updated_chat.last_read_message_id, + updated_chat.last_injected_context, + updated_chat.dynamic_tools, + updated_chat.organization_id, + updated_chat.plan_mode, + updated_chat.client_type, + updated_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM + updated_chat + JOIN visible_users owner ON owner.id = updated_chat.owner_id +) +SELECT * +FROM chats_expanded; -- name: GetStaleChats :many -- Find chats that appear stuck and need recovery: @@ -818,7 +1406,7 @@ RETURNING SELECT * FROM - chats + chats_expanded WHERE (status = 'running'::chat_status AND heartbeat_at < @stale_threshold::timestamptz) @@ -828,7 +1416,7 @@ WHERE AND updated_at < @stale_threshold::timestamptz AND EXISTS ( SELECT 1 FROM chat_queued_messages cqm - WHERE cqm.chat_id = chats.id + WHERE cqm.chat_id = chats_expanded.id )); -- name: UpdateChatHeartbeats :many @@ -1017,7 +1605,49 @@ LIMIT 1; -- name: GetChatByIDForUpdate :one -SELECT * FROM chats WHERE id = @id::uuid FOR UPDATE; +WITH locked_chat AS ( + SELECT * + FROM chats + WHERE id = @id::uuid + FOR UPDATE +), +chats_expanded AS ( + SELECT + locked_chat.id, + locked_chat.owner_id, + locked_chat.workspace_id, + locked_chat.title, + locked_chat.status, + locked_chat.worker_id, + locked_chat.started_at, + locked_chat.heartbeat_at, + locked_chat.created_at, + locked_chat.updated_at, + locked_chat.parent_chat_id, + locked_chat.root_chat_id, + locked_chat.last_model_config_id, + locked_chat.archived, + locked_chat.last_error, + locked_chat.mode, + locked_chat.mcp_server_ids, + locked_chat.labels, + locked_chat.build_id, + locked_chat.agent_id, + locked_chat.pin_order, + locked_chat.last_read_message_id, + locked_chat.last_injected_context, + locked_chat.dynamic_tools, + locked_chat.organization_id, + locked_chat.plan_mode, + locked_chat.client_type, + locked_chat.last_turn_summary, + owner.username AS owner_username, + owner.name AS owner_name + FROM locked_chat + JOIN visible_users owner ON owner.id = locked_chat.owner_id +) +SELECT * +FROM chats_expanded; -- name: AcquireStaleChatDiffStatuses :many WITH acquired AS ( @@ -1386,7 +2016,7 @@ WHERE gme.user_id = @user_id::uuid -- name: GetChatsByWorkspaceIDs :many SELECT * -FROM chats +FROM chats_expanded WHERE archived = false AND workspace_id = ANY(@ids::uuid[]) ORDER BY workspace_id, updated_at DESC; @@ -1501,7 +2131,7 @@ FROM chat_model_configs WHERE deleted = false; -- name: GetActiveChatsByAgentID :many SELECT * -FROM chats +FROM chats_expanded WHERE agent_id = @agent_id::uuid AND archived = false -- Active statuses only: waiting, pending, running, paused, diff --git a/coderd/database/sqlc.yaml b/coderd/database/sqlc.yaml index ef50155c9f..2a4787f6ea 100644 --- a/coderd/database/sqlc.yaml +++ b/coderd/database/sqlc.yaml @@ -68,6 +68,9 @@ sql: - column: "chats.labels" go_type: type: "StringMap" + - column: "chats_expanded.labels" + go_type: + type: "StringMap" - column: "users.rbac_roles" go_type: "github.com/lib/pq.StringArray" - column: "templates.user_acl" @@ -163,6 +166,8 @@ sql: type: "NullDecimal" package: "decimal" rename: + chat: ChatTable + chats_expanded: Chat group_member: GroupMemberTable group_members_expanded: GroupMember template: TemplateTable diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md index 33606da12a..a126aac611 100644 --- a/docs/admin/security/audit-logs.md +++ b/docs/admin/security/audit-logs.md @@ -22,7 +22,7 @@ We track the following resources: | AuditOAuthConvertState
| |
FieldTracked
created_attrue
expires_attrue
from_login_typetrue
to_login_typetrue
user_idtrue
| | Group
create, write, delete | |
FieldTracked
avatar_urltrue
chat_spend_limit_microstrue
display_nametrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
sourcefalse
| | AuditableOrganizationMember
| |
FieldTracked
created_attrue
organization_idfalse
rolestrue
updated_attrue
user_idtrue
usernametrue
| -| Chat
create, write | |
FieldTracked
agent_idfalse
archivedtrue
build_idfalse
client_typefalse
created_atfalse
dynamic_toolsfalse
heartbeat_atfalse
idtrue
labelstrue
last_errorfalse
last_injected_contextfalse
last_model_config_idfalse
last_read_message_idfalse
last_turn_summaryfalse
mcp_server_idstrue
modetrue
organization_idfalse
owner_idtrue
parent_chat_idfalse
pin_ordertrue
plan_modefalse
root_chat_idfalse
started_atfalse
statusfalse
titletrue
updated_atfalse
worker_idfalse
workspace_idtrue
| +| Chat
create, write | |
FieldTracked
agent_idfalse
archivedtrue
build_idfalse
client_typefalse
created_atfalse
dynamic_toolsfalse
heartbeat_atfalse
idtrue
labelstrue
last_errorfalse
last_injected_contextfalse
last_model_config_idfalse
last_read_message_idfalse
last_turn_summaryfalse
mcp_server_idstrue
modetrue
organization_idfalse
owner_idtrue
owner_namefalse
owner_usernamefalse
parent_chat_idfalse
pin_ordertrue
plan_modefalse
root_chat_idfalse
started_atfalse
statusfalse
titletrue
updated_atfalse
worker_idfalse
workspace_idtrue
| | CustomRole
| |
FieldTracked
created_atfalse
display_nametrue
idfalse
is_systemfalse
member_permissionstrue
nametrue
org_permissionstrue
organization_idfalse
site_permissionstrue
updated_atfalse
user_permissionstrue
| | GitSSHKey
create | |
FieldTracked
created_atfalse
private_keytrue
public_keytrue
updated_atfalse
user_idtrue
| | GroupSyncSettings
| |
FieldTracked
auto_create_missing_groupstrue
fieldtrue
legacy_group_name_mappingfalse
mappingtrue
regex_filtertrue
| diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index 03996e744c..3dc614a529 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -406,6 +406,8 @@ var auditableResourcesTypes = map[any]map[string]Action{ &database.Chat{}: { "id": ActionTrack, "owner_id": ActionTrack, + "owner_username": ActionIgnore, + "owner_name": ActionIgnore, "organization_id": ActionIgnore, // Never changes after creation. "workspace_id": ActionTrack, "build_id": ActionIgnore, // Internal lifecycle.