diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go
index 7b85bd3323..aba2c0d52f 100644
--- a/coderd/apidoc/docs.go
+++ b/coderd/apidoc/docs.go
@@ -17132,7 +17132,8 @@ const docTemplate = `{
"paused",
"completed",
"error",
- "requires_action"
+ "requires_action",
+ "interrupting"
],
"x-enum-varnames": [
"ChatStatusWaiting",
@@ -17141,7 +17142,8 @@ const docTemplate = `{
"ChatStatusPaused",
"ChatStatusCompleted",
"ChatStatusError",
- "ChatStatusRequiresAction"
+ "ChatStatusRequiresAction",
+ "ChatStatusInterrupting"
]
},
"codersdk.ChatStreamActionRequired": {
diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json
index 0bff17f6dc..0a6290d7fe 100644
--- a/coderd/apidoc/swagger.json
+++ b/coderd/apidoc/swagger.json
@@ -15457,7 +15457,8 @@
"paused",
"completed",
"error",
- "requires_action"
+ "requires_action",
+ "interrupting"
],
"x-enum-varnames": [
"ChatStatusWaiting",
@@ -15466,7 +15467,8 @@
"ChatStatusPaused",
"ChatStatusCompleted",
"ChatStatusError",
- "ChatStatusRequiresAction"
+ "ChatStatusRequiresAction",
+ "ChatStatusInterrupting"
]
},
"codersdk.ChatStreamActionRequired": {
diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go
index 086a35d229..6542ec0be2 100644
--- a/coderd/database/dbauthz/dbauthz.go
+++ b/coderd/database/dbauthz/dbauthz.go
@@ -1813,6 +1813,10 @@ func (q *querier) CountAuditLogs(ctx context.Context, arg database.CountAuditLog
return q.db.CountAuthorizedAuditLogs(ctx, arg, prep)
}
+func (q *querier) CountChatQueuedMessages(ctx context.Context, chatID uuid.UUID) (int64, error) {
+ panic("not implemented")
+}
+
func (q *querier) CountConnectionLogs(ctx context.Context, arg database.CountConnectionLogsParams) (int64, error) {
// Just like the actual query, shortcut if the user is an owner.
err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceConnectionLog)
@@ -1903,6 +1907,10 @@ func (q *querier) DeleteAPIKeysByUserID(ctx context.Context, userID uuid.UUID) e
return q.db.DeleteAPIKeysByUserID(ctx, userID)
}
+func (q *querier) DeleteAllChatHeartbeats(ctx context.Context, chatID uuid.UUID) error {
+ panic("not implemented")
+}
+
func (q *querier) DeleteAllChatQueuedMessages(ctx context.Context, chatID uuid.UUID) error {
chat, err := q.db.GetChatByID(ctx, chatID)
if err != nil {
@@ -1914,6 +1922,10 @@ func (q *querier) DeleteAllChatQueuedMessages(ctx context.Context, chatID uuid.U
return q.db.DeleteAllChatQueuedMessages(ctx, chatID)
}
+func (q *querier) DeleteAllChatQueuedMessagesReturningCount(ctx context.Context, chatID uuid.UUID) (int64, error) {
+ panic("not implemented")
+}
+
func (q *querier) DeleteAllTailnetTunnels(ctx context.Context, arg database.DeleteAllTailnetTunnelsParams) ([]database.DeleteAllTailnetTunnelsRow, error) {
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceTailnetCoordinator); err != nil {
return nil, err
@@ -1960,6 +1972,10 @@ func (q *querier) DeleteChatDebugDataByChatID(ctx context.Context, arg database.
return q.db.DeleteChatDebugDataByChatID(ctx, arg)
}
+func (q *querier) DeleteChatHeartbeats(ctx context.Context, arg database.DeleteChatHeartbeatsParams) (int64, error) {
+ panic("not implemented")
+}
+
func (q *querier) DeleteChatModelConfigByID(ctx context.Context, id uuid.UUID) error {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
return err
@@ -1992,6 +2008,10 @@ func (q *querier) DeleteChatQueuedMessage(ctx context.Context, arg database.Dele
return q.db.DeleteChatQueuedMessage(ctx, arg)
}
+func (q *querier) DeleteChatQueuedMessageReturningCount(ctx context.Context, arg database.DeleteChatQueuedMessageReturningCountParams) (int64, error) {
+ panic("not implemented")
+}
+
func (q *querier) DeleteChatUsageLimitGroupOverride(ctx context.Context, groupID uuid.UUID) error {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
return err
@@ -2265,6 +2285,10 @@ func (q *querier) DeleteRuntimeConfig(ctx context.Context, key string) error {
return q.db.DeleteRuntimeConfig(ctx, key)
}
+func (q *querier) DeleteStaleChatHeartbeats(ctx context.Context, staleSeconds int32) (int64, error) {
+ panic("not implemented")
+}
+
func (q *querier) DeleteTailnetPeer(ctx context.Context, arg database.DeleteTailnetPeerParams) (database.DeleteTailnetPeerRow, error) {
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceTailnetCoordinator); err != nil {
return database.DeleteTailnetPeerRow{}, err
@@ -2750,6 +2774,10 @@ func (q *querier) GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUI
return q.db.GetAuthorizationUserRoles(ctx, userID)
}
+func (q *querier) GetAutoArchiveInactiveChatCandidates(ctx context.Context, arg database.GetAutoArchiveInactiveChatCandidatesParams) ([]database.GetAutoArchiveInactiveChatCandidatesRow, error) {
+ panic("not implemented")
+}
+
// TODO (PR #24810): Replace rbac.ResourceAuditLog with dedicated boundary_log resource type.
func (q *querier) GetBoundaryLogByID(ctx context.Context, id uuid.UUID) (database.BoundaryLog, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceAuditLog); err != nil {
@@ -2974,6 +3002,10 @@ func (q *querier) GetChatExploreModelOverride(ctx context.Context) (string, erro
return q.db.GetChatExploreModelOverride(ctx)
}
+func (q *querier) GetChatFamilyIDsByRootID(ctx context.Context, id uuid.UUID) ([]uuid.UUID, error) {
+ panic("not implemented")
+}
+
func (q *querier) GetChatFileByID(ctx context.Context, id uuid.UUID) (database.ChatFile, error) {
file, err := q.db.GetChatFileByID(ctx, id)
if err != nil {
@@ -3040,6 +3072,10 @@ func (q *querier) GetChatGeneralModelOverride(ctx context.Context) (string, erro
return q.db.GetChatGeneralModelOverride(ctx)
}
+func (q *querier) GetChatHeartbeat(ctx context.Context, arg database.GetChatHeartbeatParams) (database.ChatHeartbeat, error) {
+ panic("not implemented")
+}
+
func (q *querier) GetChatIncludeDefaultSystemPrompt(ctx context.Context) (bool, error) {
// The include-default-system-prompt flag is a deployment-wide setting read
// during chat creation by every authenticated user, so no RBAC policy
@@ -3148,6 +3184,14 @@ func (q *querier) GetChatPlanModeInstructions(ctx context.Context) (string, erro
return q.db.GetChatPlanModeInstructions(ctx)
}
+func (q *querier) GetChatQueuedMessageByID(ctx context.Context, arg database.GetChatQueuedMessageByIDParams) (database.ChatQueuedMessage, error) {
+ panic("not implemented")
+}
+
+func (q *querier) GetChatQueuedMessageHead(ctx context.Context, chatID uuid.UUID) (database.ChatQueuedMessage, error) {
+ panic("not implemented")
+}
+
func (q *querier) GetChatQueuedMessages(ctx context.Context, chatID uuid.UUID) ([]database.ChatQueuedMessage, error) {
_, err := q.GetChatByID(ctx, chatID)
if err != nil {
@@ -3156,6 +3200,10 @@ func (q *querier) GetChatQueuedMessages(ctx context.Context, chatID uuid.UUID) (
return q.db.GetChatQueuedMessages(ctx, chatID)
}
+func (q *querier) GetChatQueuedMessagesByPosition(ctx context.Context, chatID uuid.UUID) ([]database.ChatQueuedMessage, error) {
+ panic("not implemented")
+}
+
func (q *querier) GetChatRetentionDays(ctx context.Context) (int32, error) {
// Chat retention is a deployment-wide config read by dbpurge.
// Only requires a valid actor in context.
@@ -3237,6 +3285,10 @@ func (q *querier) GetChatUserPromptsByChatID(ctx context.Context, arg database.G
return q.db.GetChatUserPromptsByChatID(ctx, arg)
}
+func (q *querier) GetChatWorkerAcquisitionCandidates(ctx context.Context, arg database.GetChatWorkerAcquisitionCandidatesParams) ([]database.GetChatWorkerAcquisitionCandidatesRow, error) {
+ panic("not implemented")
+}
+
func (q *querier) GetChatWorkspaceTTL(ctx context.Context) (string, error) {
// The workspace-TTL setting is a deployment-wide value read by any
// authenticated chat user. We only require that an explicit actor is
@@ -3259,6 +3311,10 @@ func (q *querier) GetChatsByChatFileID(ctx context.Context, fileID uuid.UUID) ([
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetChatsByChatFileID)(ctx, fileID)
}
+func (q *querier) GetChatsByIDsForRunnerSync(ctx context.Context, ids []uuid.UUID) ([]database.Chat, error) {
+ panic("not implemented")
+}
+
func (q *querier) GetChatsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]database.Chat, error) {
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetChatsByWorkspaceIDs)(ctx, ids)
}
@@ -3329,6 +3385,10 @@ func (q *querier) GetDERPMeshKey(ctx context.Context) (string, error) {
return q.db.GetDERPMeshKey(ctx)
}
+func (q *querier) GetDatabaseNow(ctx context.Context) (time.Time, error) {
+ panic("not implemented")
+}
+
func (q *querier) GetDefaultChatModelConfig(ctx context.Context) (database.ChatModelConfig, error) {
// Reading the default model config is needed for chat creation.
// TODO(CODAGT-161): scope this check when org context is available.
@@ -5365,6 +5425,10 @@ func (q *querier) GetWorkspacesForWorkspaceMetrics(ctx context.Context) ([]datab
return q.db.GetWorkspacesForWorkspaceMetrics(ctx)
}
+func (q *querier) IncrementChatGenerationAttempt(ctx context.Context, id uuid.UUID) (int64, error) {
+ panic("not implemented")
+}
+
func (q *querier) InsertAIBridgeInterception(ctx context.Context, arg database.InsertAIBridgeInterceptionParams) (database.AIBridgeInterception, error) {
return insert(q.log, q.auth, rbac.ResourceAibridgeInterception.WithOwner(arg.InitiatorID.String()), q.db.InsertAIBridgeInterception)(ctx, arg)
}
@@ -5513,6 +5577,14 @@ func (q *querier) InsertChatQueuedMessage(ctx context.Context, arg database.Inse
return q.db.InsertChatQueuedMessage(ctx, arg)
}
+func (q *querier) InsertChatQueuedMessageWithCreator(ctx context.Context, arg database.InsertChatQueuedMessageWithCreatorParams) (database.ChatQueuedMessage, error) {
+ panic("not implemented")
+}
+
+func (q *querier) InsertChatWithSnapshot(ctx context.Context, arg database.InsertChatWithSnapshotParams) (database.Chat, error) {
+ panic("not implemented")
+}
+
func (q *querier) InsertCryptoKey(ctx context.Context, arg database.InsertCryptoKeyParams) (database.CryptoKey, error) {
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceCryptoKey); err != nil {
return database.CryptoKey{}, err
@@ -6071,6 +6143,10 @@ func (q *querier) InsertWorkspaceResourceMetadata(ctx context.Context, arg datab
return q.db.InsertWorkspaceResourceMetadata(ctx, arg)
}
+func (q *querier) IsChatHeartbeatStale(ctx context.Context, arg database.IsChatHeartbeatStaleParams) (bool, error) {
+ panic("not implemented")
+}
+
func (q *querier) LinkChatFiles(ctx context.Context, arg database.LinkChatFilesParams) (int32, error) {
chat, err := q.db.GetChatByID(ctx, arg.ChatID)
if err != nil {
@@ -6257,6 +6333,10 @@ func (q *querier) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID
return q.db.ListWorkspaceAgentPortShares(ctx, workspaceID)
}
+func (q *querier) LockChatAndBumpSnapshotVersion(ctx context.Context, id uuid.UUID) (database.Chat, error) {
+ panic("not implemented")
+}
+
func (q *querier) MarkAllInboxNotificationsAsRead(ctx context.Context, arg database.MarkAllInboxNotificationsAsReadParams) error {
resource := rbac.ResourceInboxNotification.WithOwner(arg.UserID.String())
@@ -6363,6 +6443,10 @@ func (q *querier) ReorderChatQueuedMessageToFront(ctx context.Context, arg datab
return q.db.ReorderChatQueuedMessageToFront(ctx, arg)
}
+func (q *querier) ReorderChatQueuedMessageToHead(ctx context.Context, arg database.ReorderChatQueuedMessageToHeadParams) (int64, error) {
+ panic("not implemented")
+}
+
func (q *querier) ResolveUserChatSpendLimit(ctx context.Context, arg database.ResolveUserChatSpendLimitParams) (database.ResolveUserChatSpendLimitRow, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceChat.WithOwner(arg.UserID.String())); err != nil {
return database.ResolveUserChatSpendLimitRow{}, err
@@ -6605,6 +6689,10 @@ func (q *querier) UpdateChatDebugStep(ctx context.Context, arg database.UpdateCh
return q.db.UpdateChatDebugStep(ctx, arg)
}
+func (q *querier) UpdateChatExecutionState(ctx context.Context, arg database.UpdateChatExecutionStateParams) (database.Chat, error) {
+ panic("not implemented")
+}
+
func (q *querier) UpdateChatHeartbeats(ctx context.Context, arg database.UpdateChatHeartbeatsParams) ([]uuid.UUID, error) {
// The batch heartbeat is a system-level operation filtered by
// worker_id. Authorization is enforced by the AsChatd context
@@ -8066,6 +8154,14 @@ func (q *querier) UpsertChatGeneralModelOverride(ctx context.Context, value stri
return q.db.UpsertChatGeneralModelOverride(ctx, value)
}
+func (q *querier) UpsertChatHeartbeat(ctx context.Context, arg database.UpsertChatHeartbeatParams) error {
+ panic("not implemented")
+}
+
+func (q *querier) UpsertChatHeartbeats(ctx context.Context, arg database.UpsertChatHeartbeatsParams) error {
+ panic("not implemented")
+}
+
func (q *querier) UpsertChatIncludeDefaultSystemPrompt(ctx context.Context, includeDefaultSystemPrompt bool) error {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
return err
diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go
index fd4537ccec..ecbe1bd363 100644
--- a/coderd/database/dbmetrics/querymetrics.go
+++ b/coderd/database/dbmetrics/querymetrics.go
@@ -177,14 +177,6 @@ func (m queryMetricsStore) ArchiveUnusedTemplateVersions(ctx context.Context, ar
return r0, r1
}
-func (m queryMetricsStore) AutoArchiveInactiveChats(ctx context.Context, arg database.AutoArchiveInactiveChatsParams) ([]database.AutoArchiveInactiveChatsRow, error) {
- start := time.Now()
- r0, r1 := m.s.AutoArchiveInactiveChats(ctx, arg)
- m.queryLatencies.WithLabelValues("AutoArchiveInactiveChats").Observe(time.Since(start).Seconds())
- m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "AutoArchiveInactiveChats").Inc()
- return r0, r1
-}
-
func (m queryMetricsStore) BackoffChatDiffStatus(ctx context.Context, arg database.BackoffChatDiffStatusParams) error {
start := time.Now()
r0 := m.s.BackoffChatDiffStatus(ctx, arg)
@@ -321,6 +313,14 @@ func (m queryMetricsStore) CountAuditLogs(ctx context.Context, arg database.Coun
return r0, r1
}
+func (m queryMetricsStore) CountChatQueuedMessages(ctx context.Context, chatID uuid.UUID) (int64, error) {
+ start := time.Now()
+ r0, r1 := m.s.CountChatQueuedMessages(ctx, chatID)
+ m.queryLatencies.WithLabelValues("CountChatQueuedMessages").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "CountChatQueuedMessages").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) CountConnectionLogs(ctx context.Context, arg database.CountConnectionLogsParams) (int64, error) {
start := time.Now()
r0, r1 := m.s.CountConnectionLogs(ctx, arg)
@@ -409,6 +409,14 @@ func (m queryMetricsStore) DeleteAPIKeysByUserID(ctx context.Context, userID uui
return r0
}
+func (m queryMetricsStore) DeleteAllChatHeartbeats(ctx context.Context, chatID uuid.UUID) error {
+ start := time.Now()
+ r0 := m.s.DeleteAllChatHeartbeats(ctx, chatID)
+ m.queryLatencies.WithLabelValues("DeleteAllChatHeartbeats").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "DeleteAllChatHeartbeats").Inc()
+ return r0
+}
+
func (m queryMetricsStore) DeleteAllChatQueuedMessages(ctx context.Context, chatID uuid.UUID) error {
start := time.Now()
r0 := m.s.DeleteAllChatQueuedMessages(ctx, chatID)
@@ -417,6 +425,14 @@ func (m queryMetricsStore) DeleteAllChatQueuedMessages(ctx context.Context, chat
return r0
}
+func (m queryMetricsStore) DeleteAllChatQueuedMessagesReturningCount(ctx context.Context, chatID uuid.UUID) (int64, error) {
+ start := time.Now()
+ r0, r1 := m.s.DeleteAllChatQueuedMessagesReturningCount(ctx, chatID)
+ m.queryLatencies.WithLabelValues("DeleteAllChatQueuedMessagesReturningCount").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "DeleteAllChatQueuedMessagesReturningCount").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) DeleteAllTailnetTunnels(ctx context.Context, arg database.DeleteAllTailnetTunnelsParams) ([]database.DeleteAllTailnetTunnelsRow, error) {
start := time.Now()
r0, r1 := m.s.DeleteAllTailnetTunnels(ctx, arg)
@@ -457,6 +473,14 @@ func (m queryMetricsStore) DeleteChatDebugDataByChatID(ctx context.Context, chat
return r0, r1
}
+func (m queryMetricsStore) DeleteChatHeartbeats(ctx context.Context, arg database.DeleteChatHeartbeatsParams) (int64, error) {
+ start := time.Now()
+ r0, r1 := m.s.DeleteChatHeartbeats(ctx, arg)
+ m.queryLatencies.WithLabelValues("DeleteChatHeartbeats").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "DeleteChatHeartbeats").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) DeleteChatModelConfigByID(ctx context.Context, id uuid.UUID) error {
start := time.Now()
r0 := m.s.DeleteChatModelConfigByID(ctx, id)
@@ -489,6 +513,14 @@ func (m queryMetricsStore) DeleteChatQueuedMessage(ctx context.Context, arg data
return r0
}
+func (m queryMetricsStore) DeleteChatQueuedMessageReturningCount(ctx context.Context, arg database.DeleteChatQueuedMessageReturningCountParams) (int64, error) {
+ start := time.Now()
+ r0, r1 := m.s.DeleteChatQueuedMessageReturningCount(ctx, arg)
+ m.queryLatencies.WithLabelValues("DeleteChatQueuedMessageReturningCount").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "DeleteChatQueuedMessageReturningCount").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) DeleteChatUsageLimitGroupOverride(ctx context.Context, groupID uuid.UUID) error {
start := time.Now()
r0 := m.s.DeleteChatUsageLimitGroupOverride(ctx, groupID)
@@ -769,6 +801,14 @@ func (m queryMetricsStore) DeleteRuntimeConfig(ctx context.Context, key string)
return r0
}
+func (m queryMetricsStore) DeleteStaleChatHeartbeats(ctx context.Context, staleSeconds int32) (int64, error) {
+ start := time.Now()
+ r0, r1 := m.s.DeleteStaleChatHeartbeats(ctx, staleSeconds)
+ m.queryLatencies.WithLabelValues("DeleteStaleChatHeartbeats").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "DeleteStaleChatHeartbeats").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) DeleteTailnetPeer(ctx context.Context, arg database.DeleteTailnetPeerParams) (database.DeleteTailnetPeerRow, error) {
start := time.Now()
r0, r1 := m.s.DeleteTailnetPeer(ctx, arg)
@@ -1257,6 +1297,14 @@ func (m queryMetricsStore) GetAuthorizationUserRoles(ctx context.Context, userID
return r0, r1
}
+func (m queryMetricsStore) GetAutoArchiveInactiveChatCandidates(ctx context.Context, arg database.GetAutoArchiveInactiveChatCandidatesParams) ([]database.GetAutoArchiveInactiveChatCandidatesRow, error) {
+ start := time.Now()
+ r0, r1 := m.s.GetAutoArchiveInactiveChatCandidates(ctx, arg)
+ m.queryLatencies.WithLabelValues("GetAutoArchiveInactiveChatCandidates").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetAutoArchiveInactiveChatCandidates").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) GetBoundaryLogByID(ctx context.Context, id uuid.UUID) (database.BoundaryLog, error) {
start := time.Now()
r0, r1 := m.s.GetBoundaryLogByID(ctx, id)
@@ -1305,6 +1353,14 @@ func (m queryMetricsStore) GetChatByID(ctx context.Context, id uuid.UUID) (datab
return r0, r1
}
+func (m queryMetricsStore) GetChatByIDForShare(ctx context.Context, id uuid.UUID) (database.Chat, error) {
+ start := time.Now()
+ r0, r1 := m.s.GetChatByIDForShare(ctx, id)
+ m.queryLatencies.WithLabelValues("GetChatByIDForShare").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetChatByIDForShare").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) GetChatByIDForUpdate(ctx context.Context, id uuid.UUID) (database.Chat, error) {
start := time.Now()
r0, r1 := m.s.GetChatByIDForUpdate(ctx, id)
@@ -1433,6 +1489,14 @@ func (m queryMetricsStore) GetChatExploreModelOverride(ctx context.Context) (str
return r0, r1
}
+func (m queryMetricsStore) GetChatFamilyIDsByRootID(ctx context.Context, id uuid.UUID) ([]uuid.UUID, error) {
+ start := time.Now()
+ r0, r1 := m.s.GetChatFamilyIDsByRootID(ctx, id)
+ m.queryLatencies.WithLabelValues("GetChatFamilyIDsByRootID").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetChatFamilyIDsByRootID").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) GetChatFileByID(ctx context.Context, id uuid.UUID) (database.ChatFile, error) {
start := time.Now()
r0, r1 := m.s.GetChatFileByID(ctx, id)
@@ -1465,6 +1529,14 @@ func (m queryMetricsStore) GetChatGeneralModelOverride(ctx context.Context) (str
return r0, r1
}
+func (m queryMetricsStore) GetChatHeartbeat(ctx context.Context, arg database.GetChatHeartbeatParams) (database.ChatHeartbeat, error) {
+ start := time.Now()
+ r0, r1 := m.s.GetChatHeartbeat(ctx, arg)
+ m.queryLatencies.WithLabelValues("GetChatHeartbeat").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetChatHeartbeat").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) GetChatIncludeDefaultSystemPrompt(ctx context.Context) (bool, error) {
start := time.Now()
r0, r1 := m.s.GetChatIncludeDefaultSystemPrompt(ctx)
@@ -1561,6 +1633,22 @@ func (m queryMetricsStore) GetChatPlanModeInstructions(ctx context.Context) (str
return r0, r1
}
+func (m queryMetricsStore) GetChatQueuedMessageByID(ctx context.Context, arg database.GetChatQueuedMessageByIDParams) (database.ChatQueuedMessage, error) {
+ start := time.Now()
+ r0, r1 := m.s.GetChatQueuedMessageByID(ctx, arg)
+ m.queryLatencies.WithLabelValues("GetChatQueuedMessageByID").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetChatQueuedMessageByID").Inc()
+ return r0, r1
+}
+
+func (m queryMetricsStore) GetChatQueuedMessageHead(ctx context.Context, chatID uuid.UUID) (database.ChatQueuedMessage, error) {
+ start := time.Now()
+ r0, r1 := m.s.GetChatQueuedMessageHead(ctx, chatID)
+ m.queryLatencies.WithLabelValues("GetChatQueuedMessageHead").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetChatQueuedMessageHead").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) GetChatQueuedMessages(ctx context.Context, chatID uuid.UUID) ([]database.ChatQueuedMessage, error) {
start := time.Now()
r0, r1 := m.s.GetChatQueuedMessages(ctx, chatID)
@@ -1569,6 +1657,14 @@ func (m queryMetricsStore) GetChatQueuedMessages(ctx context.Context, chatID uui
return r0, r1
}
+func (m queryMetricsStore) GetChatQueuedMessagesByPosition(ctx context.Context, chatID uuid.UUID) ([]database.ChatQueuedMessage, error) {
+ start := time.Now()
+ r0, r1 := m.s.GetChatQueuedMessagesByPosition(ctx, chatID)
+ m.queryLatencies.WithLabelValues("GetChatQueuedMessagesByPosition").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetChatQueuedMessagesByPosition").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) GetChatRetentionDays(ctx context.Context) (int32, error) {
start := time.Now()
r0, r1 := m.s.GetChatRetentionDays(ctx)
@@ -1641,6 +1737,14 @@ func (m queryMetricsStore) GetChatUserPromptsByChatID(ctx context.Context, arg d
return r0, r1
}
+func (m queryMetricsStore) GetChatWorkerAcquisitionCandidates(ctx context.Context, arg database.GetChatWorkerAcquisitionCandidatesParams) ([]database.GetChatWorkerAcquisitionCandidatesRow, error) {
+ start := time.Now()
+ r0, r1 := m.s.GetChatWorkerAcquisitionCandidates(ctx, arg)
+ m.queryLatencies.WithLabelValues("GetChatWorkerAcquisitionCandidates").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetChatWorkerAcquisitionCandidates").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) GetChatWorkspaceTTL(ctx context.Context) (string, error) {
start := time.Now()
r0, r1 := m.s.GetChatWorkspaceTTL(ctx)
@@ -1665,6 +1769,14 @@ func (m queryMetricsStore) GetChatsByChatFileID(ctx context.Context, fileID uuid
return r0, r1
}
+func (m queryMetricsStore) GetChatsByIDsForRunnerSync(ctx context.Context, ids []uuid.UUID) ([]database.Chat, error) {
+ start := time.Now()
+ r0, r1 := m.s.GetChatsByIDsForRunnerSync(ctx, ids)
+ m.queryLatencies.WithLabelValues("GetChatsByIDsForRunnerSync").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetChatsByIDsForRunnerSync").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) GetChatsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]database.Chat, error) {
start := time.Now()
r0, r1 := m.s.GetChatsByWorkspaceIDs(ctx, ids)
@@ -1737,6 +1849,14 @@ func (m queryMetricsStore) GetDERPMeshKey(ctx context.Context) (string, error) {
return r0, r1
}
+func (m queryMetricsStore) GetDatabaseNow(ctx context.Context) (time.Time, error) {
+ start := time.Now()
+ r0, r1 := m.s.GetDatabaseNow(ctx)
+ m.queryLatencies.WithLabelValues("GetDatabaseNow").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetDatabaseNow").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) GetDefaultChatModelConfig(ctx context.Context) (database.ChatModelConfig, error) {
start := time.Now()
r0, r1 := m.s.GetDefaultChatModelConfig(ctx)
@@ -3665,6 +3785,14 @@ func (m queryMetricsStore) GetWorkspacesForWorkspaceMetrics(ctx context.Context)
return r0, r1
}
+func (m queryMetricsStore) IncrementChatGenerationAttempt(ctx context.Context, id uuid.UUID) (int64, error) {
+ start := time.Now()
+ r0, r1 := m.s.IncrementChatGenerationAttempt(ctx, id)
+ m.queryLatencies.WithLabelValues("IncrementChatGenerationAttempt").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "IncrementChatGenerationAttempt").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) InsertAIBridgeInterception(ctx context.Context, arg database.InsertAIBridgeInterceptionParams) (database.AIBridgeInterception, error) {
start := time.Now()
r0, r1 := m.s.InsertAIBridgeInterception(ctx, arg)
@@ -3817,6 +3945,14 @@ func (m queryMetricsStore) InsertChatQueuedMessage(ctx context.Context, arg data
return r0, r1
}
+func (m queryMetricsStore) InsertChatQueuedMessageWithCreator(ctx context.Context, arg database.InsertChatQueuedMessageWithCreatorParams) (database.ChatQueuedMessage, error) {
+ start := time.Now()
+ r0, r1 := m.s.InsertChatQueuedMessageWithCreator(ctx, arg)
+ m.queryLatencies.WithLabelValues("InsertChatQueuedMessageWithCreator").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "InsertChatQueuedMessageWithCreator").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) InsertCryptoKey(ctx context.Context, arg database.InsertCryptoKeyParams) (database.CryptoKey, error) {
start := time.Now()
r0, r1 := m.s.InsertCryptoKey(ctx, arg)
@@ -4313,6 +4449,14 @@ func (m queryMetricsStore) InsertWorkspaceResourceMetadata(ctx context.Context,
return r0, r1
}
+func (m queryMetricsStore) IsChatHeartbeatStale(ctx context.Context, arg database.IsChatHeartbeatStaleParams) (bool, error) {
+ start := time.Now()
+ r0, r1 := m.s.IsChatHeartbeatStale(ctx, arg)
+ m.queryLatencies.WithLabelValues("IsChatHeartbeatStale").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "IsChatHeartbeatStale").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) LinkChatFiles(ctx context.Context, arg database.LinkChatFilesParams) (int32, error) {
start := time.Now()
r0, r1 := m.s.LinkChatFiles(ctx, arg)
@@ -4497,6 +4641,14 @@ func (m queryMetricsStore) ListWorkspaceAgentPortShares(ctx context.Context, wor
return r0, r1
}
+func (m queryMetricsStore) LockChatAndBumpSnapshotVersion(ctx context.Context, id uuid.UUID) (database.Chat, error) {
+ start := time.Now()
+ r0, r1 := m.s.LockChatAndBumpSnapshotVersion(ctx, id)
+ m.queryLatencies.WithLabelValues("LockChatAndBumpSnapshotVersion").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "LockChatAndBumpSnapshotVersion").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) MarkAllInboxNotificationsAsRead(ctx context.Context, arg database.MarkAllInboxNotificationsAsReadParams) error {
start := time.Now()
r0 := m.s.MarkAllInboxNotificationsAsRead(ctx, arg)
@@ -4585,6 +4737,14 @@ func (m queryMetricsStore) ReorderChatQueuedMessageToFront(ctx context.Context,
return r0, r1
}
+func (m queryMetricsStore) ReorderChatQueuedMessageToHead(ctx context.Context, arg database.ReorderChatQueuedMessageToHeadParams) (int64, error) {
+ start := time.Now()
+ r0, r1 := m.s.ReorderChatQueuedMessageToHead(ctx, arg)
+ m.queryLatencies.WithLabelValues("ReorderChatQueuedMessageToHead").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "ReorderChatQueuedMessageToHead").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) ResolveUserChatSpendLimit(ctx context.Context, userID database.ResolveUserChatSpendLimitParams) (database.ResolveUserChatSpendLimitRow, error) {
start := time.Now()
r0, r1 := m.s.ResolveUserChatSpendLimit(ctx, userID)
@@ -4777,6 +4937,14 @@ func (m queryMetricsStore) UpdateChatDebugStep(ctx context.Context, arg database
return r0, r1
}
+func (m queryMetricsStore) UpdateChatExecutionState(ctx context.Context, arg database.UpdateChatExecutionStateParams) (database.Chat, error) {
+ start := time.Now()
+ r0, r1 := m.s.UpdateChatExecutionState(ctx, arg)
+ m.queryLatencies.WithLabelValues("UpdateChatExecutionState").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpdateChatExecutionState").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) UpdateChatHeartbeats(ctx context.Context, arg database.UpdateChatHeartbeatsParams) ([]uuid.UUID, error) {
start := time.Now()
r0, r1 := m.s.UpdateChatHeartbeats(ctx, arg)
@@ -4865,6 +5033,14 @@ func (m queryMetricsStore) UpdateChatPlanModeByID(ctx context.Context, arg datab
return r0, r1
}
+func (m queryMetricsStore) UpdateChatRetryState(ctx context.Context, arg database.UpdateChatRetryStateParams) (database.Chat, error) {
+ start := time.Now()
+ r0, r1 := m.s.UpdateChatRetryState(ctx, arg)
+ m.queryLatencies.WithLabelValues("UpdateChatRetryState").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpdateChatRetryState").Inc()
+ return r0, r1
+}
+
func (m queryMetricsStore) UpdateChatStatus(ctx context.Context, arg database.UpdateChatStatusParams) (database.Chat, error) {
start := time.Now()
r0, r1 := m.s.UpdateChatStatus(ctx, arg)
@@ -5809,6 +5985,22 @@ func (m queryMetricsStore) UpsertChatGeneralModelOverride(ctx context.Context, v
return r0
}
+func (m queryMetricsStore) UpsertChatHeartbeat(ctx context.Context, arg database.UpsertChatHeartbeatParams) error {
+ start := time.Now()
+ r0 := m.s.UpsertChatHeartbeat(ctx, arg)
+ m.queryLatencies.WithLabelValues("UpsertChatHeartbeat").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpsertChatHeartbeat").Inc()
+ return r0
+}
+
+func (m queryMetricsStore) UpsertChatHeartbeats(ctx context.Context, arg database.UpsertChatHeartbeatsParams) error {
+ start := time.Now()
+ r0 := m.s.UpsertChatHeartbeats(ctx, arg)
+ m.queryLatencies.WithLabelValues("UpsertChatHeartbeats").Observe(time.Since(start).Seconds())
+ m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpsertChatHeartbeats").Inc()
+ return r0
+}
+
func (m queryMetricsStore) UpsertChatIncludeDefaultSystemPrompt(ctx context.Context, includeDefaultSystemPrompt bool) error {
start := time.Now()
r0 := m.s.UpsertChatIncludeDefaultSystemPrompt(ctx, includeDefaultSystemPrompt)
diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go
index 36f8429e8f..2af6fe1098 100644
--- a/coderd/database/dbmock/dbmock.go
+++ b/coderd/database/dbmock/dbmock.go
@@ -178,21 +178,6 @@ func (mr *MockStoreMockRecorder) ArchiveUnusedTemplateVersions(ctx, arg any) *go
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ArchiveUnusedTemplateVersions", reflect.TypeOf((*MockStore)(nil).ArchiveUnusedTemplateVersions), ctx, arg)
}
-// AutoArchiveInactiveChats mocks base method.
-func (m *MockStore) AutoArchiveInactiveChats(ctx context.Context, arg database.AutoArchiveInactiveChatsParams) ([]database.AutoArchiveInactiveChatsRow, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "AutoArchiveInactiveChats", ctx, arg)
- ret0, _ := ret[0].([]database.AutoArchiveInactiveChatsRow)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// AutoArchiveInactiveChats indicates an expected call of AutoArchiveInactiveChats.
-func (mr *MockStoreMockRecorder) AutoArchiveInactiveChats(ctx, arg any) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AutoArchiveInactiveChats", reflect.TypeOf((*MockStore)(nil).AutoArchiveInactiveChats), ctx, arg)
-}
-
// BackoffChatDiffStatus mocks base method.
func (m *MockStore) BackoffChatDiffStatus(ctx context.Context, arg database.BackoffChatDiffStatusParams) error {
m.ctrl.T.Helper()
@@ -498,6 +483,21 @@ func (mr *MockStoreMockRecorder) CountAuthorizedConnectionLogs(ctx, arg, prepare
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountAuthorizedConnectionLogs", reflect.TypeOf((*MockStore)(nil).CountAuthorizedConnectionLogs), ctx, arg, prepared)
}
+// CountChatQueuedMessages mocks base method.
+func (m *MockStore) CountChatQueuedMessages(ctx context.Context, chatID uuid.UUID) (int64, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "CountChatQueuedMessages", ctx, chatID)
+ ret0, _ := ret[0].(int64)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// CountChatQueuedMessages indicates an expected call of CountChatQueuedMessages.
+func (mr *MockStoreMockRecorder) CountChatQueuedMessages(ctx, chatID any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountChatQueuedMessages", reflect.TypeOf((*MockStore)(nil).CountChatQueuedMessages), ctx, chatID)
+}
+
// CountConnectionLogs mocks base method.
func (m *MockStore) CountConnectionLogs(ctx context.Context, arg database.CountConnectionLogsParams) (int64, error) {
m.ctrl.T.Helper()
@@ -659,6 +659,20 @@ func (mr *MockStoreMockRecorder) DeleteAPIKeysByUserID(ctx, userID any) *gomock.
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAPIKeysByUserID", reflect.TypeOf((*MockStore)(nil).DeleteAPIKeysByUserID), ctx, userID)
}
+// DeleteAllChatHeartbeats mocks base method.
+func (m *MockStore) DeleteAllChatHeartbeats(ctx context.Context, chatID uuid.UUID) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DeleteAllChatHeartbeats", ctx, chatID)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// DeleteAllChatHeartbeats indicates an expected call of DeleteAllChatHeartbeats.
+func (mr *MockStoreMockRecorder) DeleteAllChatHeartbeats(ctx, chatID any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllChatHeartbeats", reflect.TypeOf((*MockStore)(nil).DeleteAllChatHeartbeats), ctx, chatID)
+}
+
// DeleteAllChatQueuedMessages mocks base method.
func (m *MockStore) DeleteAllChatQueuedMessages(ctx context.Context, chatID uuid.UUID) error {
m.ctrl.T.Helper()
@@ -673,6 +687,21 @@ func (mr *MockStoreMockRecorder) DeleteAllChatQueuedMessages(ctx, chatID any) *g
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllChatQueuedMessages", reflect.TypeOf((*MockStore)(nil).DeleteAllChatQueuedMessages), ctx, chatID)
}
+// DeleteAllChatQueuedMessagesReturningCount mocks base method.
+func (m *MockStore) DeleteAllChatQueuedMessagesReturningCount(ctx context.Context, chatID uuid.UUID) (int64, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DeleteAllChatQueuedMessagesReturningCount", ctx, chatID)
+ ret0, _ := ret[0].(int64)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// DeleteAllChatQueuedMessagesReturningCount indicates an expected call of DeleteAllChatQueuedMessagesReturningCount.
+func (mr *MockStoreMockRecorder) DeleteAllChatQueuedMessagesReturningCount(ctx, chatID any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllChatQueuedMessagesReturningCount", reflect.TypeOf((*MockStore)(nil).DeleteAllChatQueuedMessagesReturningCount), ctx, chatID)
+}
+
// DeleteAllTailnetTunnels mocks base method.
func (m *MockStore) DeleteAllTailnetTunnels(ctx context.Context, arg database.DeleteAllTailnetTunnelsParams) ([]database.DeleteAllTailnetTunnelsRow, error) {
m.ctrl.T.Helper()
@@ -746,6 +775,21 @@ func (mr *MockStoreMockRecorder) DeleteChatDebugDataByChatID(ctx, arg any) *gomo
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteChatDebugDataByChatID", reflect.TypeOf((*MockStore)(nil).DeleteChatDebugDataByChatID), ctx, arg)
}
+// DeleteChatHeartbeats mocks base method.
+func (m *MockStore) DeleteChatHeartbeats(ctx context.Context, arg database.DeleteChatHeartbeatsParams) (int64, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DeleteChatHeartbeats", ctx, arg)
+ ret0, _ := ret[0].(int64)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// DeleteChatHeartbeats indicates an expected call of DeleteChatHeartbeats.
+func (mr *MockStoreMockRecorder) DeleteChatHeartbeats(ctx, arg any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteChatHeartbeats", reflect.TypeOf((*MockStore)(nil).DeleteChatHeartbeats), ctx, arg)
+}
+
// DeleteChatModelConfigByID mocks base method.
func (m *MockStore) DeleteChatModelConfigByID(ctx context.Context, id uuid.UUID) error {
m.ctrl.T.Helper()
@@ -802,6 +846,21 @@ func (mr *MockStoreMockRecorder) DeleteChatQueuedMessage(ctx, arg any) *gomock.C
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteChatQueuedMessage", reflect.TypeOf((*MockStore)(nil).DeleteChatQueuedMessage), ctx, arg)
}
+// DeleteChatQueuedMessageReturningCount mocks base method.
+func (m *MockStore) DeleteChatQueuedMessageReturningCount(ctx context.Context, arg database.DeleteChatQueuedMessageReturningCountParams) (int64, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DeleteChatQueuedMessageReturningCount", ctx, arg)
+ ret0, _ := ret[0].(int64)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// DeleteChatQueuedMessageReturningCount indicates an expected call of DeleteChatQueuedMessageReturningCount.
+func (mr *MockStoreMockRecorder) DeleteChatQueuedMessageReturningCount(ctx, arg any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteChatQueuedMessageReturningCount", reflect.TypeOf((*MockStore)(nil).DeleteChatQueuedMessageReturningCount), ctx, arg)
+}
+
// DeleteChatUsageLimitGroupOverride mocks base method.
func (m *MockStore) DeleteChatUsageLimitGroupOverride(ctx context.Context, groupID uuid.UUID) error {
m.ctrl.T.Helper()
@@ -1304,6 +1363,21 @@ func (mr *MockStoreMockRecorder) DeleteRuntimeConfig(ctx, key any) *gomock.Call
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRuntimeConfig", reflect.TypeOf((*MockStore)(nil).DeleteRuntimeConfig), ctx, key)
}
+// DeleteStaleChatHeartbeats mocks base method.
+func (m *MockStore) DeleteStaleChatHeartbeats(ctx context.Context, staleSeconds int32) (int64, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DeleteStaleChatHeartbeats", ctx, staleSeconds)
+ ret0, _ := ret[0].(int64)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// DeleteStaleChatHeartbeats indicates an expected call of DeleteStaleChatHeartbeats.
+func (mr *MockStoreMockRecorder) DeleteStaleChatHeartbeats(ctx, staleSeconds any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteStaleChatHeartbeats", reflect.TypeOf((*MockStore)(nil).DeleteStaleChatHeartbeats), ctx, staleSeconds)
+}
+
// DeleteTailnetPeer mocks base method.
func (m *MockStore) DeleteTailnetPeer(ctx context.Context, arg database.DeleteTailnetPeerParams) (database.DeleteTailnetPeerRow, error) {
m.ctrl.T.Helper()
@@ -2325,6 +2399,21 @@ func (mr *MockStoreMockRecorder) GetAuthorizedWorkspacesAndAgentsByOwnerID(ctx,
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthorizedWorkspacesAndAgentsByOwnerID", reflect.TypeOf((*MockStore)(nil).GetAuthorizedWorkspacesAndAgentsByOwnerID), ctx, ownerID, prepared)
}
+// GetAutoArchiveInactiveChatCandidates mocks base method.
+func (m *MockStore) GetAutoArchiveInactiveChatCandidates(ctx context.Context, arg database.GetAutoArchiveInactiveChatCandidatesParams) ([]database.GetAutoArchiveInactiveChatCandidatesRow, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetAutoArchiveInactiveChatCandidates", ctx, arg)
+ ret0, _ := ret[0].([]database.GetAutoArchiveInactiveChatCandidatesRow)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetAutoArchiveInactiveChatCandidates indicates an expected call of GetAutoArchiveInactiveChatCandidates.
+func (mr *MockStoreMockRecorder) GetAutoArchiveInactiveChatCandidates(ctx, arg any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAutoArchiveInactiveChatCandidates", reflect.TypeOf((*MockStore)(nil).GetAutoArchiveInactiveChatCandidates), ctx, arg)
+}
+
// GetBoundaryLogByID mocks base method.
func (m *MockStore) GetBoundaryLogByID(ctx context.Context, id uuid.UUID) (database.BoundaryLog, error) {
m.ctrl.T.Helper()
@@ -2415,6 +2504,21 @@ func (mr *MockStoreMockRecorder) GetChatByID(ctx, id any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatByID", reflect.TypeOf((*MockStore)(nil).GetChatByID), ctx, id)
}
+// GetChatByIDForShare mocks base method.
+func (m *MockStore) GetChatByIDForShare(ctx context.Context, id uuid.UUID) (database.Chat, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetChatByIDForShare", ctx, id)
+ ret0, _ := ret[0].(database.Chat)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetChatByIDForShare indicates an expected call of GetChatByIDForShare.
+func (mr *MockStoreMockRecorder) GetChatByIDForShare(ctx, id any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatByIDForShare", reflect.TypeOf((*MockStore)(nil).GetChatByIDForShare), ctx, id)
+}
+
// GetChatByIDForUpdate mocks base method.
func (m *MockStore) GetChatByIDForUpdate(ctx context.Context, id uuid.UUID) (database.Chat, error) {
m.ctrl.T.Helper()
@@ -2655,6 +2759,21 @@ func (mr *MockStoreMockRecorder) GetChatExploreModelOverride(ctx any) *gomock.Ca
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatExploreModelOverride", reflect.TypeOf((*MockStore)(nil).GetChatExploreModelOverride), ctx)
}
+// GetChatFamilyIDsByRootID mocks base method.
+func (m *MockStore) GetChatFamilyIDsByRootID(ctx context.Context, id uuid.UUID) ([]uuid.UUID, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetChatFamilyIDsByRootID", ctx, id)
+ ret0, _ := ret[0].([]uuid.UUID)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetChatFamilyIDsByRootID indicates an expected call of GetChatFamilyIDsByRootID.
+func (mr *MockStoreMockRecorder) GetChatFamilyIDsByRootID(ctx, id any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatFamilyIDsByRootID", reflect.TypeOf((*MockStore)(nil).GetChatFamilyIDsByRootID), ctx, id)
+}
+
// GetChatFileByID mocks base method.
func (m *MockStore) GetChatFileByID(ctx context.Context, id uuid.UUID) (database.ChatFile, error) {
m.ctrl.T.Helper()
@@ -2715,6 +2834,21 @@ func (mr *MockStoreMockRecorder) GetChatGeneralModelOverride(ctx any) *gomock.Ca
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatGeneralModelOverride", reflect.TypeOf((*MockStore)(nil).GetChatGeneralModelOverride), ctx)
}
+// GetChatHeartbeat mocks base method.
+func (m *MockStore) GetChatHeartbeat(ctx context.Context, arg database.GetChatHeartbeatParams) (database.ChatHeartbeat, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetChatHeartbeat", ctx, arg)
+ ret0, _ := ret[0].(database.ChatHeartbeat)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetChatHeartbeat indicates an expected call of GetChatHeartbeat.
+func (mr *MockStoreMockRecorder) GetChatHeartbeat(ctx, arg any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatHeartbeat", reflect.TypeOf((*MockStore)(nil).GetChatHeartbeat), ctx, arg)
+}
+
// GetChatIncludeDefaultSystemPrompt mocks base method.
func (m *MockStore) GetChatIncludeDefaultSystemPrompt(ctx context.Context) (bool, error) {
m.ctrl.T.Helper()
@@ -2895,6 +3029,36 @@ func (mr *MockStoreMockRecorder) GetChatPlanModeInstructions(ctx any) *gomock.Ca
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatPlanModeInstructions", reflect.TypeOf((*MockStore)(nil).GetChatPlanModeInstructions), ctx)
}
+// GetChatQueuedMessageByID mocks base method.
+func (m *MockStore) GetChatQueuedMessageByID(ctx context.Context, arg database.GetChatQueuedMessageByIDParams) (database.ChatQueuedMessage, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetChatQueuedMessageByID", ctx, arg)
+ ret0, _ := ret[0].(database.ChatQueuedMessage)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetChatQueuedMessageByID indicates an expected call of GetChatQueuedMessageByID.
+func (mr *MockStoreMockRecorder) GetChatQueuedMessageByID(ctx, arg any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatQueuedMessageByID", reflect.TypeOf((*MockStore)(nil).GetChatQueuedMessageByID), ctx, arg)
+}
+
+// GetChatQueuedMessageHead mocks base method.
+func (m *MockStore) GetChatQueuedMessageHead(ctx context.Context, chatID uuid.UUID) (database.ChatQueuedMessage, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetChatQueuedMessageHead", ctx, chatID)
+ ret0, _ := ret[0].(database.ChatQueuedMessage)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetChatQueuedMessageHead indicates an expected call of GetChatQueuedMessageHead.
+func (mr *MockStoreMockRecorder) GetChatQueuedMessageHead(ctx, chatID any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatQueuedMessageHead", reflect.TypeOf((*MockStore)(nil).GetChatQueuedMessageHead), ctx, chatID)
+}
+
// GetChatQueuedMessages mocks base method.
func (m *MockStore) GetChatQueuedMessages(ctx context.Context, chatID uuid.UUID) ([]database.ChatQueuedMessage, error) {
m.ctrl.T.Helper()
@@ -2910,6 +3074,21 @@ func (mr *MockStoreMockRecorder) GetChatQueuedMessages(ctx, chatID any) *gomock.
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatQueuedMessages", reflect.TypeOf((*MockStore)(nil).GetChatQueuedMessages), ctx, chatID)
}
+// GetChatQueuedMessagesByPosition mocks base method.
+func (m *MockStore) GetChatQueuedMessagesByPosition(ctx context.Context, chatID uuid.UUID) ([]database.ChatQueuedMessage, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetChatQueuedMessagesByPosition", ctx, chatID)
+ ret0, _ := ret[0].([]database.ChatQueuedMessage)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetChatQueuedMessagesByPosition indicates an expected call of GetChatQueuedMessagesByPosition.
+func (mr *MockStoreMockRecorder) GetChatQueuedMessagesByPosition(ctx, chatID any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatQueuedMessagesByPosition", reflect.TypeOf((*MockStore)(nil).GetChatQueuedMessagesByPosition), ctx, chatID)
+}
+
// GetChatRetentionDays mocks base method.
func (m *MockStore) GetChatRetentionDays(ctx context.Context) (int32, error) {
m.ctrl.T.Helper()
@@ -3045,6 +3224,21 @@ func (mr *MockStoreMockRecorder) GetChatUserPromptsByChatID(ctx, arg any) *gomoc
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatUserPromptsByChatID", reflect.TypeOf((*MockStore)(nil).GetChatUserPromptsByChatID), ctx, arg)
}
+// GetChatWorkerAcquisitionCandidates mocks base method.
+func (m *MockStore) GetChatWorkerAcquisitionCandidates(ctx context.Context, arg database.GetChatWorkerAcquisitionCandidatesParams) ([]database.GetChatWorkerAcquisitionCandidatesRow, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetChatWorkerAcquisitionCandidates", ctx, arg)
+ ret0, _ := ret[0].([]database.GetChatWorkerAcquisitionCandidatesRow)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetChatWorkerAcquisitionCandidates indicates an expected call of GetChatWorkerAcquisitionCandidates.
+func (mr *MockStoreMockRecorder) GetChatWorkerAcquisitionCandidates(ctx, arg any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatWorkerAcquisitionCandidates", reflect.TypeOf((*MockStore)(nil).GetChatWorkerAcquisitionCandidates), ctx, arg)
+}
+
// GetChatWorkspaceTTL mocks base method.
func (m *MockStore) GetChatWorkspaceTTL(ctx context.Context) (string, error) {
m.ctrl.T.Helper()
@@ -3090,6 +3284,21 @@ func (mr *MockStoreMockRecorder) GetChatsByChatFileID(ctx, fileID any) *gomock.C
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatsByChatFileID", reflect.TypeOf((*MockStore)(nil).GetChatsByChatFileID), ctx, fileID)
}
+// GetChatsByIDsForRunnerSync mocks base method.
+func (m *MockStore) GetChatsByIDsForRunnerSync(ctx context.Context, ids []uuid.UUID) ([]database.Chat, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetChatsByIDsForRunnerSync", ctx, ids)
+ ret0, _ := ret[0].([]database.Chat)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetChatsByIDsForRunnerSync indicates an expected call of GetChatsByIDsForRunnerSync.
+func (mr *MockStoreMockRecorder) GetChatsByIDsForRunnerSync(ctx, ids any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatsByIDsForRunnerSync", reflect.TypeOf((*MockStore)(nil).GetChatsByIDsForRunnerSync), ctx, ids)
+}
+
// GetChatsByWorkspaceIDs mocks base method.
func (m *MockStore) GetChatsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]database.Chat, error) {
m.ctrl.T.Helper()
@@ -3225,6 +3434,21 @@ func (mr *MockStoreMockRecorder) GetDERPMeshKey(ctx any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDERPMeshKey", reflect.TypeOf((*MockStore)(nil).GetDERPMeshKey), ctx)
}
+// GetDatabaseNow mocks base method.
+func (m *MockStore) GetDatabaseNow(ctx context.Context) (time.Time, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetDatabaseNow", ctx)
+ ret0, _ := ret[0].(time.Time)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetDatabaseNow indicates an expected call of GetDatabaseNow.
+func (mr *MockStoreMockRecorder) GetDatabaseNow(ctx any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDatabaseNow", reflect.TypeOf((*MockStore)(nil).GetDatabaseNow), ctx)
+}
+
// GetDefaultChatModelConfig mocks base method.
func (m *MockStore) GetDefaultChatModelConfig(ctx context.Context) (database.ChatModelConfig, error) {
m.ctrl.T.Helper()
@@ -6884,6 +7108,21 @@ func (mr *MockStoreMockRecorder) InTx(arg0, arg1 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InTx", reflect.TypeOf((*MockStore)(nil).InTx), arg0, arg1)
}
+// IncrementChatGenerationAttempt mocks base method.
+func (m *MockStore) IncrementChatGenerationAttempt(ctx context.Context, id uuid.UUID) (int64, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "IncrementChatGenerationAttempt", ctx, id)
+ ret0, _ := ret[0].(int64)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// IncrementChatGenerationAttempt indicates an expected call of IncrementChatGenerationAttempt.
+func (mr *MockStoreMockRecorder) IncrementChatGenerationAttempt(ctx, id any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IncrementChatGenerationAttempt", reflect.TypeOf((*MockStore)(nil).IncrementChatGenerationAttempt), ctx, id)
+}
+
// InsertAIBridgeInterception mocks base method.
func (m *MockStore) InsertAIBridgeInterception(ctx context.Context, arg database.InsertAIBridgeInterceptionParams) (database.AIBridgeInterception, error) {
m.ctrl.T.Helper()
@@ -7169,6 +7408,21 @@ func (mr *MockStoreMockRecorder) InsertChatQueuedMessage(ctx, arg any) *gomock.C
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertChatQueuedMessage", reflect.TypeOf((*MockStore)(nil).InsertChatQueuedMessage), ctx, arg)
}
+// InsertChatQueuedMessageWithCreator mocks base method.
+func (m *MockStore) InsertChatQueuedMessageWithCreator(ctx context.Context, arg database.InsertChatQueuedMessageWithCreatorParams) (database.ChatQueuedMessage, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "InsertChatQueuedMessageWithCreator", ctx, arg)
+ ret0, _ := ret[0].(database.ChatQueuedMessage)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// InsertChatQueuedMessageWithCreator indicates an expected call of InsertChatQueuedMessageWithCreator.
+func (mr *MockStoreMockRecorder) InsertChatQueuedMessageWithCreator(ctx, arg any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertChatQueuedMessageWithCreator", reflect.TypeOf((*MockStore)(nil).InsertChatQueuedMessageWithCreator), ctx, arg)
+}
+
// InsertCryptoKey mocks base method.
func (m *MockStore) InsertCryptoKey(ctx context.Context, arg database.InsertCryptoKeyParams) (database.CryptoKey, error) {
m.ctrl.T.Helper()
@@ -8084,6 +8338,21 @@ func (mr *MockStoreMockRecorder) InsertWorkspaceResourceMetadata(ctx, arg any) *
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWorkspaceResourceMetadata", reflect.TypeOf((*MockStore)(nil).InsertWorkspaceResourceMetadata), ctx, arg)
}
+// IsChatHeartbeatStale mocks base method.
+func (m *MockStore) IsChatHeartbeatStale(ctx context.Context, arg database.IsChatHeartbeatStaleParams) (bool, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "IsChatHeartbeatStale", ctx, arg)
+ ret0, _ := ret[0].(bool)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// IsChatHeartbeatStale indicates an expected call of IsChatHeartbeatStale.
+func (mr *MockStoreMockRecorder) IsChatHeartbeatStale(ctx, arg any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsChatHeartbeatStale", reflect.TypeOf((*MockStore)(nil).IsChatHeartbeatStale), ctx, arg)
+}
+
// LinkChatFiles mocks base method.
func (m *MockStore) LinkChatFiles(ctx context.Context, arg database.LinkChatFilesParams) (int32, error) {
m.ctrl.T.Helper()
@@ -8504,6 +8773,21 @@ func (mr *MockStoreMockRecorder) ListWorkspaceAgentPortShares(ctx, workspaceID a
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListWorkspaceAgentPortShares", reflect.TypeOf((*MockStore)(nil).ListWorkspaceAgentPortShares), ctx, workspaceID)
}
+// LockChatAndBumpSnapshotVersion mocks base method.
+func (m *MockStore) LockChatAndBumpSnapshotVersion(ctx context.Context, id uuid.UUID) (database.Chat, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "LockChatAndBumpSnapshotVersion", ctx, id)
+ ret0, _ := ret[0].(database.Chat)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// LockChatAndBumpSnapshotVersion indicates an expected call of LockChatAndBumpSnapshotVersion.
+func (mr *MockStoreMockRecorder) LockChatAndBumpSnapshotVersion(ctx, id any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LockChatAndBumpSnapshotVersion", reflect.TypeOf((*MockStore)(nil).LockChatAndBumpSnapshotVersion), ctx, id)
+}
+
// MarkAllInboxNotificationsAsRead mocks base method.
func (m *MockStore) MarkAllInboxNotificationsAsRead(ctx context.Context, arg database.MarkAllInboxNotificationsAsReadParams) error {
m.ctrl.T.Helper()
@@ -8696,6 +8980,21 @@ func (mr *MockStoreMockRecorder) ReorderChatQueuedMessageToFront(ctx, arg any) *
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReorderChatQueuedMessageToFront", reflect.TypeOf((*MockStore)(nil).ReorderChatQueuedMessageToFront), ctx, arg)
}
+// ReorderChatQueuedMessageToHead mocks base method.
+func (m *MockStore) ReorderChatQueuedMessageToHead(ctx context.Context, arg database.ReorderChatQueuedMessageToHeadParams) (int64, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "ReorderChatQueuedMessageToHead", ctx, arg)
+ ret0, _ := ret[0].(int64)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// ReorderChatQueuedMessageToHead indicates an expected call of ReorderChatQueuedMessageToHead.
+func (mr *MockStoreMockRecorder) ReorderChatQueuedMessageToHead(ctx, arg any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReorderChatQueuedMessageToHead", reflect.TypeOf((*MockStore)(nil).ReorderChatQueuedMessageToHead), ctx, arg)
+}
+
// ResolveUserChatSpendLimit mocks base method.
func (m *MockStore) ResolveUserChatSpendLimit(ctx context.Context, arg database.ResolveUserChatSpendLimitParams) (database.ResolveUserChatSpendLimitRow, error) {
m.ctrl.T.Helper()
@@ -9042,6 +9341,21 @@ func (mr *MockStoreMockRecorder) UpdateChatDebugStep(ctx, arg any) *gomock.Call
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateChatDebugStep", reflect.TypeOf((*MockStore)(nil).UpdateChatDebugStep), ctx, arg)
}
+// UpdateChatExecutionState mocks base method.
+func (m *MockStore) UpdateChatExecutionState(ctx context.Context, arg database.UpdateChatExecutionStateParams) (database.Chat, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "UpdateChatExecutionState", ctx, arg)
+ ret0, _ := ret[0].(database.Chat)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// UpdateChatExecutionState indicates an expected call of UpdateChatExecutionState.
+func (mr *MockStoreMockRecorder) UpdateChatExecutionState(ctx, arg any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateChatExecutionState", reflect.TypeOf((*MockStore)(nil).UpdateChatExecutionState), ctx, arg)
+}
+
// UpdateChatHeartbeats mocks base method.
func (m *MockStore) UpdateChatHeartbeats(ctx context.Context, arg database.UpdateChatHeartbeatsParams) ([]uuid.UUID, error) {
m.ctrl.T.Helper()
@@ -9205,6 +9519,21 @@ func (mr *MockStoreMockRecorder) UpdateChatPlanModeByID(ctx, arg any) *gomock.Ca
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateChatPlanModeByID", reflect.TypeOf((*MockStore)(nil).UpdateChatPlanModeByID), ctx, arg)
}
+// UpdateChatRetryState mocks base method.
+func (m *MockStore) UpdateChatRetryState(ctx context.Context, arg database.UpdateChatRetryStateParams) (database.Chat, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "UpdateChatRetryState", ctx, arg)
+ ret0, _ := ret[0].(database.Chat)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// UpdateChatRetryState indicates an expected call of UpdateChatRetryState.
+func (mr *MockStoreMockRecorder) UpdateChatRetryState(ctx, arg any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateChatRetryState", reflect.TypeOf((*MockStore)(nil).UpdateChatRetryState), ctx, arg)
+}
+
// UpdateChatStatus mocks base method.
func (m *MockStore) UpdateChatStatus(ctx context.Context, arg database.UpdateChatStatusParams) (database.Chat, error) {
m.ctrl.T.Helper()
@@ -10914,6 +11243,34 @@ func (mr *MockStoreMockRecorder) UpsertChatGeneralModelOverride(ctx, value any)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertChatGeneralModelOverride", reflect.TypeOf((*MockStore)(nil).UpsertChatGeneralModelOverride), ctx, value)
}
+// UpsertChatHeartbeat mocks base method.
+func (m *MockStore) UpsertChatHeartbeat(ctx context.Context, arg database.UpsertChatHeartbeatParams) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "UpsertChatHeartbeat", ctx, arg)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// UpsertChatHeartbeat indicates an expected call of UpsertChatHeartbeat.
+func (mr *MockStoreMockRecorder) UpsertChatHeartbeat(ctx, arg any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertChatHeartbeat", reflect.TypeOf((*MockStore)(nil).UpsertChatHeartbeat), ctx, arg)
+}
+
+// UpsertChatHeartbeats mocks base method.
+func (m *MockStore) UpsertChatHeartbeats(ctx context.Context, arg database.UpsertChatHeartbeatsParams) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "UpsertChatHeartbeats", ctx, arg)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// UpsertChatHeartbeats indicates an expected call of UpsertChatHeartbeats.
+func (mr *MockStoreMockRecorder) UpsertChatHeartbeats(ctx, arg any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertChatHeartbeats", reflect.TypeOf((*MockStore)(nil).UpsertChatHeartbeats), ctx, arg)
+}
+
// UpsertChatIncludeDefaultSystemPrompt mocks base method.
func (m *MockStore) UpsertChatIncludeDefaultSystemPrompt(ctx context.Context, includeDefaultSystemPrompt bool) error {
m.ctrl.T.Helper()
diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql
index 7fe8b7b80f..c8211cc869 100644
--- a/coderd/database/dump.sql
+++ b/coderd/database/dump.sql
@@ -333,7 +333,8 @@ CREATE TYPE chat_status AS ENUM (
'paused',
'completed',
'error',
- 'requires_action'
+ 'requires_action',
+ 'interrupting'
);
CREATE TYPE connection_status AS ENUM (
@@ -706,6 +707,29 @@ BEGIN
END;
$$;
+CREATE FUNCTION bump_chat_queue_version_on_queued_message_change() RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+DECLARE
+ changed_chat_id uuid;
+BEGIN
+ IF TG_OP = 'DELETE' THEN
+ changed_chat_id = OLD.chat_id;
+ ELSE
+ changed_chat_id = NEW.chat_id;
+ END IF;
+
+ UPDATE chats
+ SET queue_version = snapshot_version
+ WHERE id = changed_chat_id;
+
+ IF TG_OP = 'DELETE' THEN
+ RETURN OLD;
+ END IF;
+ RETURN NEW;
+END;
+$$;
+
CREATE FUNCTION check_workspace_agent_name_unique() RETURNS trigger
LANGUAGE plpgsql
AS $$
@@ -1175,6 +1199,103 @@ BEGIN
END;
$$;
+CREATE FUNCTION set_chat_message_revision_before() RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+DECLARE
+ chat_snapshot_version bigint;
+BEGIN
+ IF TG_OP = 'INSERT' AND NEW.revision IS NOT NULL THEN
+ RAISE EXCEPTION 'chat_messages.revision must be assigned by trigger';
+ END IF;
+
+ IF TG_OP = 'UPDATE' THEN
+ IF OLD.chat_id IS DISTINCT FROM NEW.chat_id THEN
+ RAISE EXCEPTION 'chat_messages.chat_id is immutable';
+ END IF;
+
+ IF OLD.revision IS DISTINCT FROM NEW.revision THEN
+ RAISE EXCEPTION 'chat_messages.revision must be assigned by trigger';
+ END IF;
+
+ IF OLD IS NOT DISTINCT FROM NEW THEN
+ RETURN NEW;
+ END IF;
+ END IF;
+
+ SELECT snapshot_version INTO chat_snapshot_version
+ FROM chats WHERE id = NEW.chat_id;
+
+ IF chat_snapshot_version IS NULL THEN
+ RAISE EXCEPTION 'chat % does not exist', NEW.chat_id;
+ END IF;
+
+ NEW.revision = chat_snapshot_version;
+ RETURN NEW;
+END;
+$$;
+
+CREATE FUNCTION sync_chat_retry_state() RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ IF OLD.retry_state_version IS DISTINCT FROM NEW.retry_state_version THEN
+ RAISE EXCEPTION 'chats.retry_state_version must be assigned by trigger';
+ END IF;
+
+ IF NEW.generation_attempt IS DISTINCT FROM OLD.generation_attempt THEN
+ NEW.retry_state = NULL;
+ END IF;
+
+ IF NEW.retry_state IS DISTINCT FROM OLD.retry_state THEN
+ NEW.retry_state_version = NEW.snapshot_version;
+ END IF;
+
+ RETURN NEW;
+END;
+$$;
+
+CREATE FUNCTION update_chat_history_after_message_insert() RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ UPDATE chats c
+ SET history_version = c.snapshot_version,
+ generation_attempt = 0
+ FROM (
+ SELECT DISTINCT chat_id FROM chat_message_history_new_rows
+ ) AS affected
+ WHERE c.id = affected.chat_id
+ AND (
+ c.history_version IS DISTINCT FROM c.snapshot_version
+ OR c.generation_attempt <> 0
+ );
+ RETURN NULL;
+END;
+$$;
+
+CREATE FUNCTION update_chat_history_after_message_update() RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ UPDATE chats c
+ SET history_version = c.snapshot_version,
+ generation_attempt = 0
+ FROM (
+ SELECT DISTINCT n.chat_id
+ FROM chat_message_history_new_rows n
+ JOIN chat_message_history_old_rows o ON o.id = n.id
+ WHERE o IS DISTINCT FROM n
+ ) AS affected
+ WHERE c.id = affected.chat_id
+ AND (
+ c.history_version IS DISTINCT FROM c.snapshot_version
+ OR c.generation_attempt <> 0
+ );
+ RETURN NULL;
+END;
+$$;
+
CREATE TABLE ai_model_prices (
provider text NOT NULL,
model text NOT NULL,
@@ -1533,6 +1654,12 @@ CREATE TABLE chat_files (
data bytea NOT NULL
);
+CREATE UNLOGGED TABLE chat_heartbeats (
+ chat_id uuid NOT NULL,
+ runner_id uuid NOT NULL,
+ heartbeat_at timestamp with time zone NOT NULL
+);
+
CREATE TABLE chat_messages (
id bigint NOT NULL,
chat_id uuid NOT NULL,
@@ -1554,7 +1681,8 @@ CREATE TABLE chat_messages (
total_cost_micros bigint,
runtime_ms bigint,
deleted boolean DEFAULT false NOT NULL,
- provider_response_id text
+ provider_response_id text,
+ revision bigint NOT NULL
);
CREATE SEQUENCE chat_messages_id_seq
@@ -1588,12 +1716,21 @@ CREATE TABLE chat_model_configs (
CONSTRAINT chat_model_configs_context_limit_check CHECK ((context_limit > 0))
);
+CREATE SEQUENCE chat_queued_messages_position_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
CREATE TABLE chat_queued_messages (
id bigint NOT NULL,
chat_id uuid NOT NULL,
content jsonb NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
- model_config_id uuid
+ model_config_id uuid,
+ "position" bigint DEFAULT nextval('chat_queued_messages_position_seq'::regclass) NOT NULL,
+ created_by uuid NOT NULL
);
CREATE SEQUENCE chat_queued_messages_id_seq
@@ -1658,6 +1795,14 @@ CREATE TABLE chats (
last_turn_summary text,
user_acl jsonb DEFAULT '{}'::jsonb NOT NULL,
group_acl jsonb DEFAULT '{}'::jsonb NOT NULL,
+ snapshot_version bigint DEFAULT 1 NOT NULL,
+ history_version bigint DEFAULT 0 NOT NULL,
+ queue_version bigint DEFAULT 0 NOT NULL,
+ generation_attempt bigint DEFAULT 0 NOT NULL,
+ retry_state jsonb,
+ retry_state_version bigint DEFAULT 0 NOT NULL,
+ runner_id uuid,
+ requires_action_deadline_at timestamp with time zone,
CONSTRAINT chat_acl_only_on_root_chats CHECK ((((parent_chat_id IS NULL) AND (root_chat_id IS NULL)) OR ((user_acl = '{}'::jsonb) AND (group_acl = '{}'::jsonb)))),
CONSTRAINT chat_group_acl_not_null_jsonb CHECK (((group_acl IS NOT NULL) AND (jsonb_typeof(group_acl) = 'object'::text))),
CONSTRAINT chat_user_acl_not_null_jsonb CHECK (((user_acl IS NOT NULL) AND (jsonb_typeof(user_acl) = 'object'::text))),
@@ -1745,6 +1890,14 @@ CREATE VIEW chats_expanded AS
c.plan_mode,
c.client_type,
c.last_turn_summary,
+ c.snapshot_version,
+ c.history_version,
+ c.queue_version,
+ c.generation_attempt,
+ c.retry_state,
+ c.retry_state_version,
+ c.runner_id,
+ c.requires_action_deadline_at,
COALESCE(root.user_acl, c.user_acl) AS user_acl,
COALESCE(root.group_acl, c.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -3689,6 +3842,9 @@ ALTER TABLE ONLY chat_file_links
ALTER TABLE ONLY chat_files
ADD CONSTRAINT chat_files_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY chat_heartbeats
+ ADD CONSTRAINT chat_heartbeats_pkey PRIMARY KEY (chat_id, runner_id);
+
ALTER TABLE ONLY chat_messages
ADD CONSTRAINT chat_messages_pkey PRIMARY KEY (id);
@@ -4022,6 +4178,8 @@ CREATE INDEX api_keys_last_used_idx ON api_keys USING btree (last_used DESC);
COMMENT ON INDEX api_keys_last_used_idx IS 'Index for optimizing api_keys queries filtering by last_used';
+CREATE INDEX chat_heartbeats_heartbeat_at_idx ON chat_heartbeats USING btree (heartbeat_at);
+
CREATE INDEX idx_agent_stats_created_at ON workspace_agent_stats USING btree (created_at);
CREATE INDEX idx_agent_stats_user_id ON workspace_agent_stats USING btree (user_id);
@@ -4134,6 +4292,8 @@ CREATE UNIQUE INDEX idx_chat_model_configs_single_default ON chat_model_configs
CREATE INDEX idx_chat_queued_messages_chat_id ON chat_queued_messages USING btree (chat_id);
+CREATE INDEX idx_chat_queued_messages_chat_position_id ON chat_queued_messages USING btree (chat_id, "position", id);
+
CREATE INDEX idx_chats_agent_id ON chats USING btree (agent_id) WHERE (agent_id IS NOT NULL);
CREATE INDEX idx_chats_auto_archive_candidates ON chats USING btree (created_at) WHERE ((archived = false) AND (pin_order = 0) AND (parent_chat_id IS NULL));
@@ -4378,6 +4538,12 @@ COMMENT ON TRIGGER remove_organization_member_custom_role ON custom_roles IS 'Wh
CREATE TRIGGER trigger_aggregate_usage_event AFTER INSERT ON usage_events FOR EACH ROW EXECUTE FUNCTION aggregate_usage_event();
+CREATE TRIGGER trigger_bump_chat_queue_version_on_queued_message_delete AFTER DELETE ON chat_queued_messages FOR EACH ROW EXECUTE FUNCTION bump_chat_queue_version_on_queued_message_change();
+
+CREATE TRIGGER trigger_bump_chat_queue_version_on_queued_message_insert AFTER INSERT ON chat_queued_messages FOR EACH ROW EXECUTE FUNCTION bump_chat_queue_version_on_queued_message_change();
+
+CREATE TRIGGER trigger_bump_chat_queue_version_on_queued_message_update AFTER UPDATE OF content, model_config_id, "position", created_by ON chat_queued_messages FOR EACH ROW EXECUTE FUNCTION bump_chat_queue_version_on_queued_message_change();
+
CREATE TRIGGER trigger_delete_group_members_on_org_member_delete BEFORE DELETE ON organization_members FOR EACH ROW EXECUTE FUNCTION delete_group_members_on_org_member_delete();
CREATE TRIGGER trigger_delete_oauth2_provider_app_token AFTER DELETE ON oauth2_provider_app_tokens FOR EACH ROW EXECUTE FUNCTION delete_deleted_oauth2_provider_app_token_api_key();
@@ -4388,6 +4554,16 @@ CREATE TRIGGER trigger_insert_organization_system_roles AFTER INSERT ON organiza
CREATE TRIGGER trigger_nullify_next_start_at_on_workspace_autostart_modificati AFTER UPDATE ON workspaces FOR EACH ROW EXECUTE FUNCTION nullify_next_start_at_on_workspace_autostart_modification();
+CREATE TRIGGER trigger_set_chat_message_revision_on_insert BEFORE INSERT ON chat_messages FOR EACH ROW EXECUTE FUNCTION set_chat_message_revision_before();
+
+CREATE TRIGGER trigger_set_chat_message_revision_on_update BEFORE UPDATE ON chat_messages FOR EACH ROW EXECUTE FUNCTION set_chat_message_revision_before();
+
+CREATE TRIGGER trigger_sync_chat_retry_state BEFORE UPDATE OF retry_state, retry_state_version, generation_attempt ON chats FOR EACH ROW EXECUTE FUNCTION sync_chat_retry_state();
+
+CREATE TRIGGER trigger_update_chat_history_after_message_insert AFTER INSERT ON chat_messages REFERENCING NEW TABLE AS chat_message_history_new_rows FOR EACH STATEMENT EXECUTE FUNCTION update_chat_history_after_message_insert();
+
+CREATE TRIGGER trigger_update_chat_history_after_message_update AFTER UPDATE ON chat_messages REFERENCING OLD TABLE AS chat_message_history_old_rows NEW TABLE AS chat_message_history_new_rows FOR EACH STATEMENT EXECUTE FUNCTION update_chat_history_after_message_update();
+
CREATE TRIGGER trigger_update_users AFTER INSERT OR UPDATE ON users FOR EACH ROW WHEN ((new.deleted = true)) EXECUTE FUNCTION delete_deleted_user_resources();
CREATE TRIGGER trigger_upsert_user_links BEFORE INSERT OR UPDATE ON user_links FOR EACH ROW EXECUTE FUNCTION insert_user_links_fail_if_user_deleted();
@@ -4453,6 +4629,9 @@ ALTER TABLE ONLY chat_files
ALTER TABLE ONLY chat_files
ADD CONSTRAINT chat_files_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE;
+ALTER TABLE ONLY chat_heartbeats
+ ADD CONSTRAINT chat_heartbeats_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY chat_messages
ADD CONSTRAINT chat_messages_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
diff --git a/coderd/database/foreign_key_constraint.go b/coderd/database/foreign_key_constraint.go
index 47dba3d673..1467f12302 100644
--- a/coderd/database/foreign_key_constraint.go
+++ b/coderd/database/foreign_key_constraint.go
@@ -21,6 +21,7 @@ const (
ForeignKeyChatFileLinksFileID ForeignKeyConstraint = "chat_file_links_file_id_fkey" // ALTER TABLE ONLY chat_file_links ADD CONSTRAINT chat_file_links_file_id_fkey FOREIGN KEY (file_id) REFERENCES chat_files(id) ON DELETE CASCADE;
ForeignKeyChatFilesOrganizationID ForeignKeyConstraint = "chat_files_organization_id_fkey" // ALTER TABLE ONLY chat_files ADD CONSTRAINT chat_files_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
ForeignKeyChatFilesOwnerID ForeignKeyConstraint = "chat_files_owner_id_fkey" // ALTER TABLE ONLY chat_files ADD CONSTRAINT chat_files_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE;
+ ForeignKeyChatHeartbeatsChatID ForeignKeyConstraint = "chat_heartbeats_chat_id_fkey" // ALTER TABLE ONLY chat_heartbeats ADD CONSTRAINT chat_heartbeats_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
ForeignKeyChatMessagesChatID ForeignKeyConstraint = "chat_messages_chat_id_fkey" // ALTER TABLE ONLY chat_messages ADD CONSTRAINT chat_messages_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
ForeignKeyChatMessagesModelConfigID ForeignKeyConstraint = "chat_messages_model_config_id_fkey" // ALTER TABLE ONLY chat_messages ADD CONSTRAINT chat_messages_model_config_id_fkey FOREIGN KEY (model_config_id) REFERENCES chat_model_configs(id);
ForeignKeyChatModelConfigsAiProviderID ForeignKeyConstraint = "chat_model_configs_ai_provider_id_fkey" // ALTER TABLE ONLY chat_model_configs ADD CONSTRAINT chat_model_configs_ai_provider_id_fkey FOREIGN KEY (ai_provider_id) REFERENCES ai_providers(id);
diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go
index 74aa53859c..2beb0ca403 100644
--- a/coderd/database/modelqueries.go
+++ b/coderd/database/modelqueries.go
@@ -824,6 +824,8 @@ func (q *sqlQuerier) GetAuthorizedChats(ctx context.Context, arg GetChatsParams,
&i.Chat.HistoryVersion,
&i.Chat.QueueVersion,
&i.Chat.GenerationAttempt,
+ &i.Chat.RetryState,
+ &i.Chat.RetryStateVersion,
&i.Chat.RunnerID,
&i.Chat.RequiresActionDeadlineAt,
&i.Chat.UserACL,
@@ -897,6 +899,8 @@ func (q *sqlQuerier) GetAuthorizedChatsByChatFileID(ctx context.Context, fileID
&i.HistoryVersion,
&i.QueueVersion,
&i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
&i.RunnerID,
&i.RequiresActionDeadlineAt,
&i.UserACL,
diff --git a/coderd/database/models.go b/coderd/database/models.go
index 593d89e4d1..49d3aeb5e0 100644
--- a/coderd/database/models.go
+++ b/coderd/database/models.go
@@ -1543,6 +1543,7 @@ const (
ChatStatusCompleted ChatStatus = "completed"
ChatStatusError ChatStatus = "error"
ChatStatusRequiresAction ChatStatus = "requires_action"
+ ChatStatusInterrupting ChatStatus = "interrupting"
)
func (e *ChatStatus) Scan(src interface{}) error {
@@ -1588,7 +1589,8 @@ func (e ChatStatus) Valid() bool {
ChatStatusPaused,
ChatStatusCompleted,
ChatStatusError,
- ChatStatusRequiresAction:
+ ChatStatusRequiresAction,
+ ChatStatusInterrupting:
return true
}
return false
@@ -1603,6 +1605,7 @@ func AllChatStatusValues() []ChatStatus {
ChatStatusCompleted,
ChatStatusError,
ChatStatusRequiresAction,
+ ChatStatusInterrupting,
}
}
@@ -4564,38 +4567,46 @@ type BoundaryUsageStat struct {
}
type Chat 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"`
- UserACL ChatACL `db:"user_acl" json:"user_acl"`
- GroupACL ChatACL `db:"group_acl" json:"group_acl"`
- OwnerUsername string `db:"owner_username" json:"owner_username"`
- OwnerName string `db:"owner_name" json:"owner_name"`
+ 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"`
+ SnapshotVersion int64 `db:"snapshot_version" json:"snapshot_version"`
+ HistoryVersion int64 `db:"history_version" json:"history_version"`
+ QueueVersion int64 `db:"queue_version" json:"queue_version"`
+ GenerationAttempt int64 `db:"generation_attempt" json:"generation_attempt"`
+ RetryState pqtype.NullRawMessage `db:"retry_state" json:"retry_state"`
+ RetryStateVersion int64 `db:"retry_state_version" json:"retry_state_version"`
+ RunnerID uuid.NullUUID `db:"runner_id" json:"runner_id"`
+ RequiresActionDeadlineAt sql.NullTime `db:"requires_action_deadline_at" json:"requires_action_deadline_at"`
+ UserACL ChatACL `db:"user_acl" json:"user_acl"`
+ GroupACL ChatACL `db:"group_acl" json:"group_acl"`
+ OwnerUsername string `db:"owner_username" json:"owner_username"`
+ OwnerName string `db:"owner_name" json:"owner_name"`
}
type ChatDebugRun struct {
@@ -4677,6 +4688,12 @@ type ChatFileLink struct {
FileID uuid.UUID `db:"file_id" json:"file_id"`
}
+type ChatHeartbeat struct {
+ ChatID uuid.UUID `db:"chat_id" json:"chat_id"`
+ RunnerID uuid.UUID `db:"runner_id" json:"runner_id"`
+ HeartbeatAt time.Time `db:"heartbeat_at" json:"heartbeat_at"`
+}
+
type ChatMessage struct {
ID int64 `db:"id" json:"id"`
ChatID uuid.UUID `db:"chat_id" json:"chat_id"`
@@ -4699,6 +4716,7 @@ type ChatMessage struct {
RuntimeMs sql.NullInt64 `db:"runtime_ms" json:"runtime_ms"`
Deleted bool `db:"deleted" json:"deleted"`
ProviderResponseID sql.NullString `db:"provider_response_id" json:"provider_response_id"`
+ Revision int64 `db:"revision" json:"revision"`
}
type ChatModelConfig struct {
@@ -4726,39 +4744,49 @@ type ChatQueuedMessage struct {
Content json.RawMessage `db:"content" json:"content"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
ModelConfigID uuid.NullUUID `db:"model_config_id" json:"model_config_id"`
+ Position int64 `db:"position" json:"position"`
+ CreatedBy uuid.UUID `db:"created_by" json:"created_by"`
}
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"`
- UserACL ChatACL `db:"user_acl" json:"user_acl"`
- GroupACL ChatACL `db:"group_acl" json:"group_acl"`
+ 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"`
+ UserACL ChatACL `db:"user_acl" json:"user_acl"`
+ GroupACL ChatACL `db:"group_acl" json:"group_acl"`
+ SnapshotVersion int64 `db:"snapshot_version" json:"snapshot_version"`
+ HistoryVersion int64 `db:"history_version" json:"history_version"`
+ QueueVersion int64 `db:"queue_version" json:"queue_version"`
+ GenerationAttempt int64 `db:"generation_attempt" json:"generation_attempt"`
+ RetryState pqtype.NullRawMessage `db:"retry_state" json:"retry_state"`
+ RetryStateVersion int64 `db:"retry_state_version" json:"retry_state_version"`
+ RunnerID uuid.NullUUID `db:"runner_id" json:"runner_id"`
+ RequiresActionDeadlineAt sql.NullTime `db:"requires_action_deadline_at" json:"requires_action_deadline_at"`
}
type ChatUsageLimitConfig struct {
diff --git a/coderd/database/querier.go b/coderd/database/querier.go
index 6b16e0771a..1e130a1b4c 100644
--- a/coderd/database/querier.go
+++ b/coderd/database/querier.go
@@ -62,14 +62,6 @@ type sqlcQuerier interface {
// Only unused template versions will be archived, which are any versions not
// referenced by the latest build of a workspace.
ArchiveUnusedTemplateVersions(ctx context.Context, arg ArchiveUnusedTemplateVersionsParams) ([]uuid.UUID, error)
- // Archives inactive root chats (pinned and already-archived chats skipped),
- // cascading to children via root_chat_id. Limits apply to roots, not total
- // rows. The Go caller passes @archive_cutoff as UTC midnight so that all
- // chats sharing the same last-activity date are archived together.
- // Used by dbpurge.
- // created_at ASC flows through to dbpurge's digest truncation; see
- // buildDigestData in dbpurge.go for the tradeoff rationale.
- AutoArchiveInactiveChats(ctx context.Context, arg AutoArchiveInactiveChatsParams) ([]AutoArchiveInactiveChatsRow, error)
BackoffChatDiffStatus(ctx context.Context, arg BackoffChatDiffStatusParams) error
BatchUpdateWorkspaceAgentMetadata(ctx context.Context, arg BatchUpdateWorkspaceAgentMetadataParams) error
BatchUpdateWorkspaceLastUsedAt(ctx context.Context, arg BatchUpdateWorkspaceLastUsedAtParams) error
@@ -89,6 +81,9 @@ type sqlcQuerier interface {
CountAIBridgeInterceptions(ctx context.Context, arg CountAIBridgeInterceptionsParams) (int64, error)
CountAIBridgeSessions(ctx context.Context, arg CountAIBridgeSessionsParams) (int64, error)
CountAuditLogs(ctx context.Context, arg CountAuditLogsParams) (int64, error)
+ // Cheap queue-length check used by ChatMachine.Update when deciding
+ // whether the chat is in a "1" sub-state.
+ CountChatQueuedMessages(ctx context.Context, chatID uuid.UUID) (int64, error)
CountConnectionLogs(ctx context.Context, arg CountConnectionLogsParams) (int64, error)
// Counts enabled, non-deleted model configs that lack both input and
// output pricing in their JSONB options.cost configuration.
@@ -105,7 +100,11 @@ type sqlcQuerier interface {
DeleteAIProviderKey(ctx context.Context, id uuid.UUID) error
DeleteAPIKeyByID(ctx context.Context, id string) error
DeleteAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error
+ // Deletes all heartbeat rows for the chat. Used during ownership
+ // transitions that abandon a lease.
+ DeleteAllChatHeartbeats(ctx context.Context, chatID uuid.UUID) error
DeleteAllChatQueuedMessages(ctx context.Context, chatID uuid.UUID) error
+ DeleteAllChatQueuedMessagesReturningCount(ctx context.Context, chatID uuid.UUID) (int64, error)
DeleteAllTailnetTunnels(ctx context.Context, arg DeleteAllTailnetTunnelsParams) ([]DeleteAllTailnetTunnelsRow, error)
// Deletes all existing webpush subscriptions.
// This should be called when the VAPID keypair is regenerated, as the old
@@ -123,10 +122,16 @@ type sqlcQuerier interface {
// window (for example, after an unarchive races with a pending
// archive-cleanup retry).
DeleteChatDebugDataByChatID(ctx context.Context, arg DeleteChatDebugDataByChatIDParams) (int64, error)
+ // Deletes heartbeat rows for the supplied (chat_id, runner_id) pairs.
+ DeleteChatHeartbeats(ctx context.Context, arg DeleteChatHeartbeatsParams) (int64, error)
DeleteChatModelConfigByID(ctx context.Context, id uuid.UUID) error
DeleteChatModelConfigsByAIProviderID(ctx context.Context, aiProviderID uuid.UUID) error
DeleteChatModelConfigsByProvider(ctx context.Context, provider string) error
DeleteChatQueuedMessage(ctx context.Context, arg DeleteChatQueuedMessageParams) error
+ // Deletes a queued message, scoped to the parent chat. Returns the
+ // number of affected rows so callers can detect missing rows without
+ // a follow-up read.
+ DeleteChatQueuedMessageReturningCount(ctx context.Context, arg DeleteChatQueuedMessageReturningCountParams) (int64, error)
DeleteChatUsageLimitGroupOverride(ctx context.Context, groupID uuid.UUID) error
DeleteChatUsageLimitUserOverride(ctx context.Context, userID uuid.UUID) error
DeleteCryptoKey(ctx context.Context, arg DeleteCryptoKeyParams) (CryptoKey, error)
@@ -195,6 +200,7 @@ type sqlcQuerier interface {
DeleteProvisionerKey(ctx context.Context, id uuid.UUID) error
DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error
DeleteRuntimeConfig(ctx context.Context, key string) error
+ DeleteStaleChatHeartbeats(ctx context.Context, staleSeconds int32) (int64, error)
DeleteTailnetPeer(ctx context.Context, arg DeleteTailnetPeerParams) (DeleteTailnetPeerRow, error)
DeleteTailnetTunnel(ctx context.Context, arg DeleteTailnetTunnelParams) (DeleteTailnetTunnelRow, error)
DeleteTask(ctx context.Context, arg DeleteTaskParams) (uuid.UUID, error)
@@ -316,6 +322,10 @@ type sqlcQuerier interface {
// This function returns roles for authorization purposes. Implied member roles
// are included.
GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (GetAuthorizationUserRolesRow, error)
+ // Returns read-only root chat candidates for state-machine-backed
+ // auto-archive. Activity is computed across the root family. The query
+ // limits roots, not total family members.
+ GetAutoArchiveInactiveChatCandidates(ctx context.Context, arg GetAutoArchiveInactiveChatCandidatesParams) ([]GetAutoArchiveInactiveChatCandidatesRow, error)
GetBoundaryLogByID(ctx context.Context, id uuid.UUID) (BoundaryLog, error)
GetBoundarySessionByID(ctx context.Context, id uuid.UUID) (BoundarySession, error)
GetChatACLByID(ctx context.Context, id uuid.UUID) (GetChatACLByIDRow, error)
@@ -327,6 +337,7 @@ type sqlcQuerier interface {
// Auto-archive window in days. 0 disables.
GetChatAutoArchiveDays(ctx context.Context, defaultAutoArchiveDays int32) (int32, error)
GetChatByID(ctx context.Context, id uuid.UUID) (Chat, error)
+ GetChatByIDForShare(ctx context.Context, id uuid.UUID) (Chat, error)
GetChatByIDForUpdate(ctx context.Context, id uuid.UUID) (Chat, error)
GetChatComputerUseProvider(ctx context.Context) (string, error)
// Per-root-chat cost breakdown for a single user within a date range.
@@ -364,6 +375,10 @@ type sqlcQuerier interface {
GetChatDiffStatusSummary(ctx context.Context) (GetChatDiffStatusSummaryRow, error)
GetChatDiffStatusesByChatIDs(ctx context.Context, chatIds []uuid.UUID) ([]ChatDiffStatus, error)
GetChatExploreModelOverride(ctx context.Context) (string, error)
+ // Returns the chat IDs of every chat in a family (root + all children)
+ // in deterministic order. The id parameter must be the root id; the
+ // query does not walk up from a child.
+ GetChatFamilyIDsByRootID(ctx context.Context, id uuid.UUID) ([]uuid.UUID, error)
GetChatFileByID(ctx context.Context, id uuid.UUID) (ChatFile, error)
// GetChatFileMetadataByChatID returns lightweight file metadata for
// all files linked to a chat. The data column is excluded to avoid
@@ -371,6 +386,7 @@ type sqlcQuerier interface {
GetChatFileMetadataByChatID(ctx context.Context, chatID uuid.UUID) ([]GetChatFileMetadataByChatIDRow, error)
GetChatFilesByIDs(ctx context.Context, ids []uuid.UUID) ([]ChatFile, error)
GetChatGeneralModelOverride(ctx context.Context) (string, error)
+ GetChatHeartbeat(ctx context.Context, arg GetChatHeartbeatParams) (ChatHeartbeat, error)
// GetChatIncludeDefaultSystemPrompt preserves the legacy default
// for deployments created before the explicit include-default toggle.
// When the toggle is unset, a non-empty custom prompt implies false;
@@ -393,7 +409,12 @@ type sqlcQuerier interface {
// personal chat model overrides. It defaults to false when unset.
GetChatPersonalModelOverridesEnabled(ctx context.Context) (bool, error)
GetChatPlanModeInstructions(ctx context.Context) (string, error)
+ GetChatQueuedMessageByID(ctx context.Context, arg GetChatQueuedMessageByIDParams) (ChatQueuedMessage, error)
+ // Returns the queue head (lowest position, then lowest id).
+ GetChatQueuedMessageHead(ctx context.Context, chatID uuid.UUID) (ChatQueuedMessage, error)
GetChatQueuedMessages(ctx context.Context, chatID uuid.UUID) ([]ChatQueuedMessage, error)
+ // Returns queued messages in state-machine order (position ASC, id ASC).
+ GetChatQueuedMessagesByPosition(ctx context.Context, chatID uuid.UUID) ([]ChatQueuedMessage, error)
// Returns the chat retention period in days. Chats archived longer
// than this and orphaned chat files older than this are purged by
// dbpurge. Returns 30 (days) when no value has been configured.
@@ -423,11 +444,18 @@ type sqlcQuerier interface {
// jsonb_array_elements never raises "cannot extract elements from a
// scalar". Backed by idx_chat_messages_user_prompts.
GetChatUserPromptsByChatID(ctx context.Context, arg GetChatUserPromptsByChatIDParams) ([]GetChatUserPromptsByChatIDRow, error)
+ // Returns worker-runnable chats whose ownership is missing or whose
+ // current runner heartbeat is stale. The runner_id IS NULL predicate is
+ // a robustness extension for inconsistent rows where a worker_id exists
+ // without a runner_id; normal missing ownership is worker_id IS NULL or
+ // a missing or stale heartbeat row.
+ GetChatWorkerAcquisitionCandidates(ctx context.Context, arg GetChatWorkerAcquisitionCandidatesParams) ([]GetChatWorkerAcquisitionCandidatesRow, error)
// Returns the global TTL for chat workspaces as a Go duration string.
// Returns "0s" (disabled) when no value has been configured.
GetChatWorkspaceTTL(ctx context.Context) (string, error)
GetChats(ctx context.Context, arg GetChatsParams) ([]GetChatsRow, error)
GetChatsByChatFileID(ctx context.Context, fileID uuid.UUID) ([]Chat, error)
+ GetChatsByIDsForRunnerSync(ctx context.Context, ids []uuid.UUID) ([]Chat, error)
GetChatsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]Chat, error)
// Retrieves chats updated after the given timestamp for telemetry
// snapshot collection. Uses updated_at so that long-running chats
@@ -444,6 +472,10 @@ type sqlcQuerier interface {
GetCryptoKeysByFeature(ctx context.Context, feature CryptoKeyFeature) ([]CryptoKey, error)
GetDBCryptKeys(ctx context.Context) ([]DBCryptKey, error)
GetDERPMeshKey(ctx context.Context) (string, error)
+ // Returns the current database timestamp. Used so transitions that
+ // record deadlines or heartbeats rely on a clock that is consistent
+ // with the database rather than the caller's local clock.
+ GetDatabaseNow(ctx context.Context) (time.Time, error)
GetDefaultChatModelConfig(ctx context.Context) (ChatModelConfig, error)
GetDefaultOrganization(ctx context.Context) (Organization, error)
GetDefaultProxyConfig(ctx context.Context) (GetDefaultProxyConfigRow, error)
@@ -907,6 +939,8 @@ type sqlcQuerier interface {
GetWorkspacesByTemplateID(ctx context.Context, templateID uuid.UUID) ([]WorkspaceTable, error)
GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]GetWorkspacesEligibleForTransitionRow, error)
GetWorkspacesForWorkspaceMetrics(ctx context.Context) ([]GetWorkspacesForWorkspaceMetricsRow, error)
+ // Increments generation_attempt and returns the resulting value.
+ IncrementChatGenerationAttempt(ctx context.Context, id uuid.UUID) (int64, error)
InsertAIBridgeInterception(ctx context.Context, arg InsertAIBridgeInterceptionParams) (AIBridgeInterception, error)
InsertAIBridgeModelThought(ctx context.Context, arg InsertAIBridgeModelThoughtParams) (AIBridgeModelThought, error)
InsertAIBridgeTokenUsage(ctx context.Context, arg InsertAIBridgeTokenUsageParams) (AIBridgeTokenUsage, error)
@@ -936,7 +970,14 @@ type sqlcQuerier interface {
InsertChatFile(ctx context.Context, arg InsertChatFileParams) (InsertChatFileRow, error)
InsertChatMessages(ctx context.Context, arg InsertChatMessagesParams) ([]ChatMessage, error)
InsertChatModelConfig(ctx context.Context, arg InsertChatModelConfigParams) (ChatModelConfig, error)
+ // Legacy queue insertion path. When no caller-supplied creator exists,
+ // preserve the created_by invariant by attributing the queued row to the
+ // chat owner.
InsertChatQueuedMessage(ctx context.Context, arg InsertChatQueuedMessageParams) (ChatQueuedMessage, error)
+ // Inserts a queued message that carries a position (from the default
+ // sequence) and an explicit created_by reference. Use this when the
+ // queued-message creator differs from the chat owner.
+ InsertChatQueuedMessageWithCreator(ctx context.Context, arg InsertChatQueuedMessageWithCreatorParams) (ChatQueuedMessage, error)
InsertCryptoKey(ctx context.Context, arg InsertCryptoKeyParams) (CryptoKey, error)
InsertCustomRole(ctx context.Context, arg InsertCustomRoleParams) (CustomRole, error)
InsertDBCryptKey(ctx context.Context, arg InsertDBCryptKeyParams) error
@@ -1016,6 +1057,11 @@ type sqlcQuerier interface {
InsertWorkspaceProxy(ctx context.Context, arg InsertWorkspaceProxyParams) (WorkspaceProxy, error)
InsertWorkspaceResource(ctx context.Context, arg InsertWorkspaceResourceParams) (WorkspaceResource, error)
InsertWorkspaceResourceMetadata(ctx context.Context, arg InsertWorkspaceResourceMetadataParams) ([]WorkspaceResourceMetadatum, error)
+ // Returns true when there is no heartbeat row for (chat_id, runner_id)
+ // or the existing row is older than @stale_seconds seconds by database
+ // time. chatstate calls this in a single query so the staleness check
+ // is atomic and does not depend on the caller's local clock.
+ IsChatHeartbeatStale(ctx context.Context, arg IsChatHeartbeatStaleParams) (bool, error)
// LinkChatFiles inserts file associations into the chat_file_links
// join table with deduplication (ON CONFLICT DO NOTHING). The INSERT
// is conditional: it only proceeds when the total number of links
@@ -1066,6 +1112,19 @@ type sqlcQuerier interface {
ListUserSecretsWithValues(ctx context.Context, userID uuid.UUID) ([]UserSecret, error)
ListUserSkillMetadataByUserID(ctx context.Context, userID uuid.UUID) ([]ListUserSkillMetadataByUserIDRow, error)
ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceAgentPortShare, error)
+ // =====================================================================
+ // chatd core state machine queries.
+ //
+ // These are consumed by the coderd/x/chatd/chatstate package. They
+ // are intentionally kept side-by-side with the legacy chatd queries
+ // above so the existing runtime keeps working while the state machine
+ // lands behind it.
+ // =====================================================================
+ // Locks the chat row with FOR UPDATE and atomically increments its
+ // snapshot_version, returning the post-bump chat. This is the single
+ // entry point ChatMachine.Update uses to acquire the row lock and
+ // allocate a new snapshot version in one round trip.
+ LockChatAndBumpSnapshotVersion(ctx context.Context, id uuid.UUID) (Chat, error)
MarkAllInboxNotificationsAsRead(ctx context.Context, arg MarkAllInboxNotificationsAsReadParams) error
OIDCClaimFieldValues(ctx context.Context, arg OIDCClaimFieldValuesParams) ([]string, error)
// OIDCClaimFields returns a list of distinct keys in the the merged_claims fields.
@@ -1090,6 +1149,9 @@ type sqlcQuerier interface {
// Mutates only created_at on the target row; ids are unchanged so
// consumers can keep tracking queued messages by id.
ReorderChatQueuedMessageToFront(ctx context.Context, arg ReorderChatQueuedMessageToFrontParams) (int64, error)
+ // Sets the target queued message's position to one less than the
+ // current minimum position for that chat, moving it to the head.
+ ReorderChatQueuedMessageToHead(ctx context.Context, arg ReorderChatQueuedMessageToHeadParams) (int64, error)
// Resolves the effective spend limit for a user using the hierarchy:
// 1. Individual user override (highest priority, applies globally across
// all organizations since it lives on the users table)
@@ -1191,6 +1253,11 @@ type sqlcQuerier interface {
// parameter keeps updated_at under the caller's clock, matching
// the injectable quartz.Clock used by FinalizeStale sweeps.
UpdateChatDebugStep(ctx context.Context, arg UpdateChatDebugStepParams) (ChatDebugStep, error)
+ // Atomically updates the execution-state-managed fields on a chat:
+ // status, archived, last_error, ownership identifiers, and the
+ // requires-action deadline. Callers compose this with transition
+ // mutations inside a single ChatMachine.Update transaction.
+ UpdateChatExecutionState(ctx context.Context, arg UpdateChatExecutionStateParams) (Chat, error)
// Bumps the heartbeat timestamp for the given set of chat IDs,
// provided they are still running and owned by the specified
// worker. Returns the IDs that were actually updated so the
@@ -1209,11 +1276,9 @@ type sqlcQuerier interface {
// Updates the cached last completed turn summary for sidebar display.
// Empty or whitespace-only summaries are stored as NULL here so direct
// query callers cannot accidentally persist blank sidebar text.
- // This intentionally preserves updated_at. The staleness guard relies on
- // every new-turn query, such as UpdateChatStatus and AcquireChats, bumping
- // updated_at. Future chat-field updates that do not bump updated_at can let
- // stale summaries persist. If this query ever bumps updated_at, later
- // goroutine summary writes will be rejected as stale.
+ // This intentionally preserves updated_at. The staleness guard uses
+ // history_version so worker lifecycle transitions that do not change the
+ // active message history cannot reject final turn summary writes.
// Two summary workers using the same freshness marker are last-write-wins.
UpdateChatLastTurnSummary(ctx context.Context, arg UpdateChatLastTurnSummaryParams) (int64, error)
UpdateChatMCPServerIDs(ctx context.Context, arg UpdateChatMCPServerIDsParams) (Chat, error)
@@ -1221,6 +1286,9 @@ type sqlcQuerier interface {
UpdateChatModelConfig(ctx context.Context, arg UpdateChatModelConfigParams) (ChatModelConfig, error)
UpdateChatPinOrder(ctx context.Context, arg UpdateChatPinOrderParams) error
UpdateChatPlanModeByID(ctx context.Context, arg UpdateChatPlanModeByIDParams) (Chat, error)
+ // Stores the client-visible retry payload. retry_state_version is
+ // assigned by trigger from the current snapshot_version.
+ UpdateChatRetryState(ctx context.Context, arg UpdateChatRetryStateParams) (Chat, error)
UpdateChatStatus(ctx context.Context, arg UpdateChatStatusParams) (Chat, error)
UpdateChatStatusPreserveUpdatedAt(ctx context.Context, arg UpdateChatStatusPreserveUpdatedAtParams) (Chat, error)
UpdateChatTitleByID(ctx context.Context, arg UpdateChatTitleByIDParams) (Chat, error)
@@ -1367,6 +1435,10 @@ type sqlcQuerier interface {
UpsertChatDiffStatusReference(ctx context.Context, arg UpsertChatDiffStatusReferenceParams) (ChatDiffStatus, error)
UpsertChatExploreModelOverride(ctx context.Context, value string) error
UpsertChatGeneralModelOverride(ctx context.Context, value string) error
+ // Upserts a heartbeat row for the (chat_id, runner_id) lease. Uses
+ // database time so callers do not depend on a local clock.
+ UpsertChatHeartbeat(ctx context.Context, arg UpsertChatHeartbeatParams) error
+ UpsertChatHeartbeats(ctx context.Context, arg UpsertChatHeartbeatsParams) error
UpsertChatIncludeDefaultSystemPrompt(ctx context.Context, includeDefaultSystemPrompt bool) error
// UpsertChatPersonalModelOverridesEnabled updates whether users may configure
// personal chat model overrides.
diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go
index 270f017e57..448f6648f8 100644
--- a/coderd/database/queries.sql.go
+++ b/coderd/database/queries.sql.go
@@ -5752,7 +5752,7 @@ 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, user_acl, group_acl
+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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
),
chats_expanded AS (
SELECT
@@ -5784,6 +5784,14 @@ chats_expanded AS (
acquired_chats.plan_mode,
acquired_chats.client_type,
acquired_chats.last_turn_summary,
+ acquired_chats.snapshot_version,
+ acquired_chats.history_version,
+ acquired_chats.queue_version,
+ acquired_chats.generation_attempt,
+ acquired_chats.retry_state,
+ acquired_chats.retry_state_version,
+ acquired_chats.runner_id,
+ acquired_chats.requires_action_deadline_at,
COALESCE(root.user_acl, acquired_chats.user_acl) AS user_acl,
COALESCE(root.group_acl, acquired_chats.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -5793,7 +5801,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(acquired_chats.root_chat_id, acquired_chats.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
`
@@ -5843,6 +5851,14 @@ func (q *sqlQuerier) AcquireChats(ctx context.Context, arg AcquireChatsParams) (
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -5985,7 +6001,7 @@ 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, user_acl, group_acl
+ 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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
),
chats_expanded AS (
SELECT
@@ -6017,6 +6033,14 @@ chats_expanded AS (
updated_chats.plan_mode,
updated_chats.client_type,
updated_chats.last_turn_summary,
+ updated_chats.snapshot_version,
+ updated_chats.history_version,
+ updated_chats.queue_version,
+ updated_chats.generation_attempt,
+ updated_chats.retry_state,
+ updated_chats.retry_state_version,
+ updated_chats.runner_id,
+ updated_chats.requires_action_deadline_at,
COALESCE(root.user_acl, updated_chats.user_acl) AS user_acl,
COALESCE(root.group_acl, updated_chats.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -6026,7 +6050,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(updated_chats.root_chat_id, updated_chats.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
ORDER BY (chats_expanded.id = $1::uuid) DESC, chats_expanded.created_at ASC, chats_expanded.id ASC
`
@@ -6069,6 +6093,14 @@ func (q *sqlQuerier) ArchiveChatByID(ctx context.Context, id uuid.UUID) ([]Chat,
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -6087,158 +6119,6 @@ func (q *sqlQuerier) ArchiveChatByID(ctx context.Context, id uuid.UUID) ([]Chat,
return items, nil
}
-const autoArchiveInactiveChats = `-- name: AutoArchiveInactiveChats :many
-WITH to_archive AS (
- SELECT
- c.id,
- -- Activity = MAX(cm.created_at) across the family, or c.created_at
- -- when the family has no non-deleted messages.
- COALESCE(activity.last_activity_at, c.created_at) AS last_activity_at
- FROM chats c
- LEFT JOIN LATERAL (
- SELECT MAX(cm.created_at) AS last_activity_at
- FROM chat_messages cm
- JOIN chats fc ON fc.id = cm.chat_id
- WHERE (fc.id = c.id OR fc.root_chat_id = c.id)
- AND cm.deleted = false
- ) activity ON TRUE
- WHERE c.archived = false
- AND c.pin_order = 0
- AND c.parent_chat_id IS NULL -- roots only
- -- Redundant filter helps the planner use the partial index on created_at.
- AND c.created_at < $1::timestamptz
- -- New active statuses must be added here to prevent archiving.
- AND c.status NOT IN ('running', 'pending', 'paused', 'requires_action')
- AND COALESCE(activity.last_activity_at, c.created_at) < $1::timestamptz
- -- Sorting by created_at lets Postgres drive the scan from the
- -- partial index instead of evaluating every LATERAL subquery
- -- before sorting. All candidates are past the cutoff, so the
- -- archive order is immaterial once the backlog drains.
- ORDER BY c.created_at ASC
- LIMIT $2
-),
-archived AS (
- UPDATE chats c
- SET archived = true, pin_order = 0, updated_at = NOW()
- FROM to_archive t
- WHERE (c.id = t.id OR c.root_chat_id = t.id) -- cascade to children
- AND c.archived = false
- RETURNING 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, c.user_acl, c.group_acl
-)
-SELECT
- a.id, a.owner_id, a.workspace_id, a.title, a.status, a.worker_id, a.started_at, a.heartbeat_at, a.created_at, a.updated_at, a.parent_chat_id, a.root_chat_id, a.last_model_config_id, a.archived, a.last_error, a.mode, a.mcp_server_ids, a.labels, a.build_id, a.agent_id, a.pin_order, a.last_read_message_id, a.last_injected_context, a.dynamic_tools, a.organization_id, a.plan_mode, a.client_type, a.last_turn_summary, a.user_acl, a.group_acl,
- -- Children inherit their root's activity so last_activity_at is never null.
- COALESCE(
- t.last_activity_at,
- (SELECT tr.last_activity_at FROM to_archive tr WHERE tr.id = a.root_chat_id),
- a.created_at
- )::timestamptz AS last_activity_at
-FROM archived a
-LEFT JOIN to_archive t ON t.id = a.id
-ORDER BY (a.root_chat_id IS NULL) DESC, a.owner_id ASC, a.created_at ASC, a.id ASC
-`
-
-type AutoArchiveInactiveChatsParams struct {
- ArchiveCutoff time.Time `db:"archive_cutoff" json:"archive_cutoff"`
- LimitCount int32 `db:"limit_count" json:"limit_count"`
-}
-
-type AutoArchiveInactiveChatsRow 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 json.RawMessage `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"`
- UserACL json.RawMessage `db:"user_acl" json:"user_acl"`
- GroupACL json.RawMessage `db:"group_acl" json:"group_acl"`
- LastActivityAt time.Time `db:"last_activity_at" json:"last_activity_at"`
-}
-
-// Archives inactive root chats (pinned and already-archived chats skipped),
-// cascading to children via root_chat_id. Limits apply to roots, not total
-// rows. The Go caller passes @archive_cutoff as UTC midnight so that all
-// chats sharing the same last-activity date are archived together.
-// Used by dbpurge.
-// created_at ASC flows through to dbpurge's digest truncation; see
-// buildDigestData in dbpurge.go for the tradeoff rationale.
-func (q *sqlQuerier) AutoArchiveInactiveChats(ctx context.Context, arg AutoArchiveInactiveChatsParams) ([]AutoArchiveInactiveChatsRow, error) {
- rows, err := q.db.QueryContext(ctx, autoArchiveInactiveChats, arg.ArchiveCutoff, arg.LimitCount)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
- var items []AutoArchiveInactiveChatsRow
- for rows.Next() {
- var i AutoArchiveInactiveChatsRow
- if err := rows.Scan(
- &i.ID,
- &i.OwnerID,
- &i.WorkspaceID,
- &i.Title,
- &i.Status,
- &i.WorkerID,
- &i.StartedAt,
- &i.HeartbeatAt,
- &i.CreatedAt,
- &i.UpdatedAt,
- &i.ParentChatID,
- &i.RootChatID,
- &i.LastModelConfigID,
- &i.Archived,
- &i.LastError,
- &i.Mode,
- pq.Array(&i.MCPServerIDs),
- &i.Labels,
- &i.BuildID,
- &i.AgentID,
- &i.PinOrder,
- &i.LastReadMessageID,
- &i.LastInjectedContext,
- &i.DynamicTools,
- &i.OrganizationID,
- &i.PlanMode,
- &i.ClientType,
- &i.LastTurnSummary,
- &i.UserACL,
- &i.GroupACL,
- &i.LastActivityAt,
- ); err != nil {
- return nil, err
- }
- items = append(items, i)
- }
- if err := rows.Close(); err != nil {
- return nil, err
- }
- if err := rows.Err(); err != nil {
- return nil, err
- }
- return items, nil
-}
-
const backoffChatDiffStatus = `-- name: BackoffChatDiffStatus :exec
UPDATE
chat_diff_statuses
@@ -6275,6 +6155,21 @@ func (q *sqlQuerier) ClearChatMessageProviderResponseIDsByChatID(ctx context.Con
return err
}
+const countChatQueuedMessages = `-- name: CountChatQueuedMessages :one
+SELECT COUNT(*)::bigint AS count
+FROM chat_queued_messages
+WHERE chat_id = $1::uuid
+`
+
+// Cheap queue-length check used by ChatMachine.Update when deciding
+// whether the chat is in a "1" sub-state.
+func (q *sqlQuerier) CountChatQueuedMessages(ctx context.Context, chatID uuid.UUID) (int64, error) {
+ row := q.db.QueryRowContext(ctx, countChatQueuedMessages, chatID)
+ var count int64
+ err := row.Scan(&count)
+ return count, err
+}
+
const countEnabledModelsWithoutPricing = `-- name: CountEnabledModelsWithoutPricing :one
SELECT COUNT(*)::bigint AS count
FROM chat_model_configs
@@ -6299,6 +6194,17 @@ func (q *sqlQuerier) CountEnabledModelsWithoutPricing(ctx context.Context) (int6
return count, err
}
+const deleteAllChatHeartbeats = `-- name: DeleteAllChatHeartbeats :exec
+DELETE FROM chat_heartbeats WHERE chat_id = $1::uuid
+`
+
+// Deletes all heartbeat rows for the chat. Used during ownership
+// transitions that abandon a lease.
+func (q *sqlQuerier) DeleteAllChatHeartbeats(ctx context.Context, chatID uuid.UUID) error {
+ _, err := q.db.ExecContext(ctx, deleteAllChatHeartbeats, chatID)
+ return err
+}
+
const deleteAllChatQueuedMessages = `-- name: DeleteAllChatQueuedMessages :exec
DELETE FROM chat_queued_messages WHERE chat_id = $1
`
@@ -6308,6 +6214,41 @@ func (q *sqlQuerier) DeleteAllChatQueuedMessages(ctx context.Context, chatID uui
return err
}
+const deleteAllChatQueuedMessagesReturningCount = `-- name: DeleteAllChatQueuedMessagesReturningCount :execrows
+DELETE FROM chat_queued_messages
+WHERE chat_id = $1::uuid
+`
+
+func (q *sqlQuerier) DeleteAllChatQueuedMessagesReturningCount(ctx context.Context, chatID uuid.UUID) (int64, error) {
+ result, err := q.db.ExecContext(ctx, deleteAllChatQueuedMessagesReturningCount, chatID)
+ if err != nil {
+ return 0, err
+ }
+ return result.RowsAffected()
+}
+
+const deleteChatHeartbeats = `-- name: DeleteChatHeartbeats :execrows
+DELETE FROM chat_heartbeats
+USING unnest($1::uuid[]) WITH ORDINALITY AS chat_ids(chat_id, ord)
+JOIN unnest($2::uuid[]) WITH ORDINALITY AS runner_ids(runner_id, ord) USING (ord)
+WHERE chat_heartbeats.chat_id = chat_ids.chat_id
+ AND chat_heartbeats.runner_id = runner_ids.runner_id
+`
+
+type DeleteChatHeartbeatsParams struct {
+ ChatIds []uuid.UUID `db:"chat_ids" json:"chat_ids"`
+ RunnerIds []uuid.UUID `db:"runner_ids" json:"runner_ids"`
+}
+
+// Deletes heartbeat rows for the supplied (chat_id, runner_id) pairs.
+func (q *sqlQuerier) DeleteChatHeartbeats(ctx context.Context, arg DeleteChatHeartbeatsParams) (int64, error) {
+ result, err := q.db.ExecContext(ctx, deleteChatHeartbeats, pq.Array(arg.ChatIds), pq.Array(arg.RunnerIds))
+ if err != nil {
+ return 0, err
+ }
+ return result.RowsAffected()
+}
+
const deleteChatQueuedMessage = `-- name: DeleteChatQueuedMessage :exec
DELETE FROM chat_queued_messages WHERE id = $1 AND chat_id = $2
`
@@ -6322,6 +6263,27 @@ func (q *sqlQuerier) DeleteChatQueuedMessage(ctx context.Context, arg DeleteChat
return err
}
+const deleteChatQueuedMessageReturningCount = `-- name: DeleteChatQueuedMessageReturningCount :execrows
+DELETE FROM chat_queued_messages
+WHERE id = $1::bigint AND chat_id = $2::uuid
+`
+
+type DeleteChatQueuedMessageReturningCountParams struct {
+ ID int64 `db:"id" json:"id"`
+ ChatID uuid.UUID `db:"chat_id" json:"chat_id"`
+}
+
+// Deletes a queued message, scoped to the parent chat. Returns the
+// number of affected rows so callers can detect missing rows without
+// a follow-up read.
+func (q *sqlQuerier) DeleteChatQueuedMessageReturningCount(ctx context.Context, arg DeleteChatQueuedMessageReturningCountParams) (int64, error) {
+ result, err := q.db.ExecContext(ctx, deleteChatQueuedMessageReturningCount, arg.ID, arg.ChatID)
+ if err != nil {
+ return 0, err
+ }
+ return result.RowsAffected()
+}
+
const deleteChatUsageLimitGroupOverride = `-- name: DeleteChatUsageLimitGroupOverride :exec
UPDATE groups SET chat_spend_limit_micros = NULL WHERE id = $1::uuid
`
@@ -6373,8 +6335,21 @@ func (q *sqlQuerier) DeleteOldChats(ctx context.Context, arg DeleteOldChatsParam
return result.RowsAffected()
}
+const deleteStaleChatHeartbeats = `-- name: DeleteStaleChatHeartbeats :execrows
+DELETE FROM chat_heartbeats
+WHERE heartbeat_at < NOW() - (INTERVAL '1 second' * $1::int)
+`
+
+func (q *sqlQuerier) DeleteStaleChatHeartbeats(ctx context.Context, staleSeconds int32) (int64, error) {
+ result, err := q.db.ExecContext(ctx, deleteStaleChatHeartbeats, staleSeconds)
+ if err != nil {
+ return 0, err
+ }
+ return result.RowsAffected()
+}
+
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
WHERE agent_id = $1::uuid
AND archived = false
@@ -6423,6 +6398,14 @@ func (q *sqlQuerier) GetActiveChatsByAgentID(ctx context.Context, agentID uuid.U
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -6441,6 +6424,152 @@ func (q *sqlQuerier) GetActiveChatsByAgentID(ctx context.Context, agentID uuid.U
return items, nil
}
+const getAutoArchiveInactiveChatCandidates = `-- name: GetAutoArchiveInactiveChatCandidates :many
+SELECT
+ 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.snapshot_version, chats_expanded.history_version, chats_expanded.queue_version, chats_expanded.generation_attempt, chats_expanded.retry_state, chats_expanded.retry_state_version, chats_expanded.runner_id, chats_expanded.requires_action_deadline_at, chats_expanded.user_acl, chats_expanded.group_acl, chats_expanded.owner_username, chats_expanded.owner_name,
+ COALESCE(activity.last_activity_at, chats_expanded.created_at)::timestamptz AS last_activity_at
+FROM chats_expanded
+LEFT JOIN LATERAL (
+ SELECT MAX(chat_messages.created_at) AS last_activity_at
+ FROM chat_messages
+ JOIN chats family_chat ON family_chat.id = chat_messages.chat_id
+ WHERE (family_chat.id = chats_expanded.id OR family_chat.root_chat_id = chats_expanded.id)
+ AND chat_messages.deleted = false
+) activity ON TRUE
+WHERE
+ chats_expanded.archived = false
+ AND chats_expanded.pin_order = 0
+ AND chats_expanded.parent_chat_id IS NULL
+ AND chats_expanded.created_at < $1::timestamptz
+ AND chats_expanded.status NOT IN (
+ 'running'::chat_status,
+ 'interrupting'::chat_status,
+ 'pending'::chat_status,
+ 'paused'::chat_status,
+ 'requires_action'::chat_status
+ )
+ AND COALESCE(activity.last_activity_at, chats_expanded.created_at) < $1::timestamptz
+ORDER BY chats_expanded.created_at ASC
+LIMIT $2::int
+`
+
+type GetAutoArchiveInactiveChatCandidatesParams struct {
+ ArchiveCutoff time.Time `db:"archive_cutoff" json:"archive_cutoff"`
+ LimitCount int32 `db:"limit_count" json:"limit_count"`
+}
+
+type GetAutoArchiveInactiveChatCandidatesRow 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"`
+ SnapshotVersion int64 `db:"snapshot_version" json:"snapshot_version"`
+ HistoryVersion int64 `db:"history_version" json:"history_version"`
+ QueueVersion int64 `db:"queue_version" json:"queue_version"`
+ GenerationAttempt int64 `db:"generation_attempt" json:"generation_attempt"`
+ RetryState pqtype.NullRawMessage `db:"retry_state" json:"retry_state"`
+ RetryStateVersion int64 `db:"retry_state_version" json:"retry_state_version"`
+ RunnerID uuid.NullUUID `db:"runner_id" json:"runner_id"`
+ RequiresActionDeadlineAt sql.NullTime `db:"requires_action_deadline_at" json:"requires_action_deadline_at"`
+ UserACL ChatACL `db:"user_acl" json:"user_acl"`
+ GroupACL ChatACL `db:"group_acl" json:"group_acl"`
+ OwnerUsername string `db:"owner_username" json:"owner_username"`
+ OwnerName string `db:"owner_name" json:"owner_name"`
+ LastActivityAt time.Time `db:"last_activity_at" json:"last_activity_at"`
+}
+
+// Returns read-only root chat candidates for state-machine-backed
+// auto-archive. Activity is computed across the root family. The query
+// limits roots, not total family members.
+func (q *sqlQuerier) GetAutoArchiveInactiveChatCandidates(ctx context.Context, arg GetAutoArchiveInactiveChatCandidatesParams) ([]GetAutoArchiveInactiveChatCandidatesRow, error) {
+ rows, err := q.db.QueryContext(ctx, getAutoArchiveInactiveChatCandidates, arg.ArchiveCutoff, arg.LimitCount)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []GetAutoArchiveInactiveChatCandidatesRow
+ for rows.Next() {
+ var i GetAutoArchiveInactiveChatCandidatesRow
+ if err := rows.Scan(
+ &i.ID,
+ &i.OwnerID,
+ &i.WorkspaceID,
+ &i.Title,
+ &i.Status,
+ &i.WorkerID,
+ &i.StartedAt,
+ &i.HeartbeatAt,
+ &i.CreatedAt,
+ &i.UpdatedAt,
+ &i.ParentChatID,
+ &i.RootChatID,
+ &i.LastModelConfigID,
+ &i.Archived,
+ &i.LastError,
+ &i.Mode,
+ pq.Array(&i.MCPServerIDs),
+ &i.Labels,
+ &i.BuildID,
+ &i.AgentID,
+ &i.PinOrder,
+ &i.LastReadMessageID,
+ &i.LastInjectedContext,
+ &i.DynamicTools,
+ &i.OrganizationID,
+ &i.PlanMode,
+ &i.ClientType,
+ &i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
+ &i.UserACL,
+ &i.GroupACL,
+ &i.OwnerUsername,
+ &i.OwnerName,
+ &i.LastActivityAt,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Close(); err != nil {
+ return nil, err
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
const getChatACLByID = `-- name: GetChatACLByID :one
SELECT
user_acl AS users,
@@ -6464,7 +6593,7 @@ func (q *sqlQuerier) GetChatACLByID(ctx context.Context, id uuid.UUID) (GetChatA
}
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
WHERE id = $1::uuid
`
@@ -6501,6 +6630,120 @@ func (q *sqlQuerier) GetChatByID(ctx context.Context, id uuid.UUID) (Chat, error
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
+ &i.UserACL,
+ &i.GroupACL,
+ &i.OwnerUsername,
+ &i.OwnerName,
+ )
+ return i, err
+}
+
+const getChatByIDForShare = `-- name: GetChatByIDForShare :one
+WITH shared_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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
+ FROM chats
+ WHERE id = $1::uuid
+ FOR SHARE
+),
+chats_expanded AS (
+ SELECT
+ shared_chat.id,
+ shared_chat.owner_id,
+ shared_chat.workspace_id,
+ shared_chat.title,
+ shared_chat.status,
+ shared_chat.worker_id,
+ shared_chat.started_at,
+ shared_chat.heartbeat_at,
+ shared_chat.created_at,
+ shared_chat.updated_at,
+ shared_chat.parent_chat_id,
+ shared_chat.root_chat_id,
+ shared_chat.last_model_config_id,
+ shared_chat.archived,
+ shared_chat.last_error,
+ shared_chat.mode,
+ shared_chat.mcp_server_ids,
+ shared_chat.labels,
+ shared_chat.build_id,
+ shared_chat.agent_id,
+ shared_chat.pin_order,
+ shared_chat.last_read_message_id,
+ shared_chat.last_injected_context,
+ shared_chat.dynamic_tools,
+ shared_chat.organization_id,
+ shared_chat.plan_mode,
+ shared_chat.client_type,
+ shared_chat.last_turn_summary,
+ shared_chat.snapshot_version,
+ shared_chat.history_version,
+ shared_chat.queue_version,
+ shared_chat.generation_attempt,
+ shared_chat.retry_state,
+ shared_chat.retry_state_version,
+ shared_chat.runner_id,
+ shared_chat.requires_action_deadline_at,
+ COALESCE(root.user_acl, shared_chat.user_acl) AS user_acl,
+ COALESCE(root.group_acl, shared_chat.group_acl) AS group_acl,
+ owner.username AS owner_username,
+ owner.name AS owner_name
+ FROM
+ shared_chat
+ LEFT JOIN chats root ON root.id = COALESCE(shared_chat.root_chat_id, shared_chat.parent_chat_id)
+ JOIN visible_users owner ON owner.id = shared_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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
+FROM chats_expanded
+`
+
+func (q *sqlQuerier) GetChatByIDForShare(ctx context.Context, id uuid.UUID) (Chat, error) {
+ row := q.db.QueryRowContext(ctx, getChatByIDForShare, id)
+ var i Chat
+ err := row.Scan(
+ &i.ID,
+ &i.OwnerID,
+ &i.WorkspaceID,
+ &i.Title,
+ &i.Status,
+ &i.WorkerID,
+ &i.StartedAt,
+ &i.HeartbeatAt,
+ &i.CreatedAt,
+ &i.UpdatedAt,
+ &i.ParentChatID,
+ &i.RootChatID,
+ &i.LastModelConfigID,
+ &i.Archived,
+ &i.LastError,
+ &i.Mode,
+ pq.Array(&i.MCPServerIDs),
+ &i.Labels,
+ &i.BuildID,
+ &i.AgentID,
+ &i.PinOrder,
+ &i.LastReadMessageID,
+ &i.LastInjectedContext,
+ &i.DynamicTools,
+ &i.OrganizationID,
+ &i.PlanMode,
+ &i.ClientType,
+ &i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -6511,7 +6754,7 @@ func (q *sqlQuerier) GetChatByID(ctx context.Context, id uuid.UUID) (Chat, error
const getChatByIDForUpdate = `-- name: GetChatByIDForUpdate :one
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, user_acl, group_acl
+ 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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
FROM chats
WHERE id = $1::uuid
FOR UPDATE
@@ -6546,6 +6789,14 @@ chats_expanded AS (
locked_chat.plan_mode,
locked_chat.client_type,
locked_chat.last_turn_summary,
+ locked_chat.snapshot_version,
+ locked_chat.history_version,
+ locked_chat.queue_version,
+ locked_chat.generation_attempt,
+ locked_chat.retry_state,
+ locked_chat.retry_state_version,
+ locked_chat.runner_id,
+ locked_chat.requires_action_deadline_at,
COALESCE(root.user_acl, locked_chat.user_acl) AS user_acl,
COALESCE(root.group_acl, locked_chat.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -6555,7 +6806,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(locked_chat.root_chat_id, locked_chat.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
`
@@ -6591,6 +6842,14 @@ func (q *sqlQuerier) GetChatByIDForUpdate(ctx context.Context, id uuid.UUID) (Ch
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -7127,9 +7386,59 @@ func (q *sqlQuerier) GetChatDiffStatusesByChatIDs(ctx context.Context, chatIds [
return items, nil
}
+const getChatFamilyIDsByRootID = `-- name: GetChatFamilyIDsByRootID :many
+SELECT id
+FROM chats
+WHERE id = $1::uuid OR root_chat_id = $1::uuid
+ORDER BY (id = $1::uuid) DESC, created_at ASC, id ASC
+`
+
+// Returns the chat IDs of every chat in a family (root + all children)
+// in deterministic order. The id parameter must be the root id; the
+// query does not walk up from a child.
+func (q *sqlQuerier) GetChatFamilyIDsByRootID(ctx context.Context, id uuid.UUID) ([]uuid.UUID, error) {
+ rows, err := q.db.QueryContext(ctx, getChatFamilyIDsByRootID, id)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []uuid.UUID
+ for rows.Next() {
+ var id uuid.UUID
+ if err := rows.Scan(&id); err != nil {
+ return nil, err
+ }
+ items = append(items, id)
+ }
+ if err := rows.Close(); err != nil {
+ return nil, err
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
+const getChatHeartbeat = `-- name: GetChatHeartbeat :one
+SELECT chat_id, runner_id, heartbeat_at FROM chat_heartbeats
+WHERE chat_id = $1::uuid AND runner_id = $2::uuid
+`
+
+type GetChatHeartbeatParams struct {
+ ChatID uuid.UUID `db:"chat_id" json:"chat_id"`
+ RunnerID uuid.UUID `db:"runner_id" json:"runner_id"`
+}
+
+func (q *sqlQuerier) GetChatHeartbeat(ctx context.Context, arg GetChatHeartbeatParams) (ChatHeartbeat, error) {
+ row := q.db.QueryRowContext(ctx, getChatHeartbeat, arg.ChatID, arg.RunnerID)
+ var i ChatHeartbeat
+ err := row.Scan(&i.ChatID, &i.RunnerID, &i.HeartbeatAt)
+ return i, err
+}
+
const getChatMessageByID = `-- name: GetChatMessageByID :one
SELECT
- id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id
+ id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id, revision
FROM
chat_messages
WHERE
@@ -7162,6 +7471,7 @@ func (q *sqlQuerier) GetChatMessageByID(ctx context.Context, id int64) (ChatMess
&i.RuntimeMs,
&i.Deleted,
&i.ProviderResponseID,
+ &i.Revision,
)
return i, err
}
@@ -7251,7 +7561,7 @@ func (q *sqlQuerier) GetChatMessageSummariesPerChat(ctx context.Context, created
const getChatMessagesByChatID = `-- name: GetChatMessagesByChatID :many
SELECT
- id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id
+ id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id, revision
FROM
chat_messages
WHERE
@@ -7299,6 +7609,7 @@ func (q *sqlQuerier) GetChatMessagesByChatID(ctx context.Context, arg GetChatMes
&i.RuntimeMs,
&i.Deleted,
&i.ProviderResponseID,
+ &i.Revision,
); err != nil {
return nil, err
}
@@ -7315,7 +7626,7 @@ func (q *sqlQuerier) GetChatMessagesByChatID(ctx context.Context, arg GetChatMes
const getChatMessagesByChatIDAscPaginated = `-- name: GetChatMessagesByChatIDAscPaginated :many
SELECT
- id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id
+ id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id, revision
FROM
chat_messages
WHERE
@@ -7366,6 +7677,7 @@ func (q *sqlQuerier) GetChatMessagesByChatIDAscPaginated(ctx context.Context, ar
&i.RuntimeMs,
&i.Deleted,
&i.ProviderResponseID,
+ &i.Revision,
); err != nil {
return nil, err
}
@@ -7382,7 +7694,7 @@ func (q *sqlQuerier) GetChatMessagesByChatIDAscPaginated(ctx context.Context, ar
const getChatMessagesByChatIDDescPaginated = `-- name: GetChatMessagesByChatIDDescPaginated :many
SELECT
- id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id
+ id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id, revision
FROM
chat_messages
WHERE
@@ -7446,6 +7758,7 @@ func (q *sqlQuerier) GetChatMessagesByChatIDDescPaginated(ctx context.Context, a
&i.RuntimeMs,
&i.Deleted,
&i.ProviderResponseID,
+ &i.Revision,
); err != nil {
return nil, err
}
@@ -7478,7 +7791,7 @@ WITH latest_compressed_summary AS (
1
)
SELECT
- id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id
+ id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id, revision
FROM
chat_messages
WHERE
@@ -7550,6 +7863,7 @@ func (q *sqlQuerier) GetChatMessagesForPromptByChatID(ctx context.Context, chatI
&i.RuntimeMs,
&i.Deleted,
&i.ProviderResponseID,
+ &i.Revision,
); err != nil {
return nil, err
}
@@ -7610,8 +7924,56 @@ func (q *sqlQuerier) GetChatModelConfigsForTelemetry(ctx context.Context) ([]Get
return items, nil
}
+const getChatQueuedMessageByID = `-- name: GetChatQueuedMessageByID :one
+SELECT id, chat_id, content, created_at, model_config_id, position, created_by FROM chat_queued_messages
+WHERE id = $1::bigint AND chat_id = $2::uuid
+`
+
+type GetChatQueuedMessageByIDParams struct {
+ ID int64 `db:"id" json:"id"`
+ ChatID uuid.UUID `db:"chat_id" json:"chat_id"`
+}
+
+func (q *sqlQuerier) GetChatQueuedMessageByID(ctx context.Context, arg GetChatQueuedMessageByIDParams) (ChatQueuedMessage, error) {
+ row := q.db.QueryRowContext(ctx, getChatQueuedMessageByID, arg.ID, arg.ChatID)
+ var i ChatQueuedMessage
+ err := row.Scan(
+ &i.ID,
+ &i.ChatID,
+ &i.Content,
+ &i.CreatedAt,
+ &i.ModelConfigID,
+ &i.Position,
+ &i.CreatedBy,
+ )
+ return i, err
+}
+
+const getChatQueuedMessageHead = `-- name: GetChatQueuedMessageHead :one
+SELECT id, chat_id, content, created_at, model_config_id, position, created_by FROM chat_queued_messages
+WHERE chat_id = $1::uuid
+ORDER BY position ASC, id ASC
+LIMIT 1
+`
+
+// Returns the queue head (lowest position, then lowest id).
+func (q *sqlQuerier) GetChatQueuedMessageHead(ctx context.Context, chatID uuid.UUID) (ChatQueuedMessage, error) {
+ row := q.db.QueryRowContext(ctx, getChatQueuedMessageHead, chatID)
+ var i ChatQueuedMessage
+ err := row.Scan(
+ &i.ID,
+ &i.ChatID,
+ &i.Content,
+ &i.CreatedAt,
+ &i.ModelConfigID,
+ &i.Position,
+ &i.CreatedBy,
+ )
+ return i, err
+}
+
const getChatQueuedMessages = `-- name: GetChatQueuedMessages :many
-SELECT id, chat_id, content, created_at, model_config_id FROM chat_queued_messages
+SELECT id, chat_id, content, created_at, model_config_id, position, created_by FROM chat_queued_messages
WHERE chat_id = $1
ORDER BY created_at ASC, id ASC
`
@@ -7631,6 +7993,46 @@ func (q *sqlQuerier) GetChatQueuedMessages(ctx context.Context, chatID uuid.UUID
&i.Content,
&i.CreatedAt,
&i.ModelConfigID,
+ &i.Position,
+ &i.CreatedBy,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Close(); err != nil {
+ return nil, err
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
+const getChatQueuedMessagesByPosition = `-- name: GetChatQueuedMessagesByPosition :many
+SELECT id, chat_id, content, created_at, model_config_id, position, created_by FROM chat_queued_messages
+WHERE chat_id = $1::uuid
+ORDER BY position ASC, id ASC
+`
+
+// Returns queued messages in state-machine order (position ASC, id ASC).
+func (q *sqlQuerier) GetChatQueuedMessagesByPosition(ctx context.Context, chatID uuid.UUID) ([]ChatQueuedMessage, error) {
+ rows, err := q.db.QueryContext(ctx, getChatQueuedMessagesByPosition, chatID)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []ChatQueuedMessage
+ for rows.Next() {
+ var i ChatQueuedMessage
+ if err := rows.Scan(
+ &i.ID,
+ &i.ChatID,
+ &i.Content,
+ &i.CreatedAt,
+ &i.ModelConfigID,
+ &i.Position,
+ &i.CreatedBy,
); err != nil {
return nil, err
}
@@ -7766,6 +8168,160 @@ func (q *sqlQuerier) GetChatUserPromptsByChatID(ctx context.Context, arg GetChat
return items, nil
}
+const getChatWorkerAcquisitionCandidates = `-- name: GetChatWorkerAcquisitionCandidates :many
+SELECT
+ 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.snapshot_version, chats_expanded.history_version, chats_expanded.queue_version, chats_expanded.generation_attempt, chats_expanded.retry_state, chats_expanded.retry_state_version, chats_expanded.runner_id, chats_expanded.requires_action_deadline_at, chats_expanded.user_acl, chats_expanded.group_acl, chats_expanded.owner_username, chats_expanded.owner_name,
+ chat_heartbeats.heartbeat_at AS current_heartbeat_at,
+ NOT EXISTS (
+ SELECT 1
+ FROM chat_heartbeats current_lease
+ WHERE current_lease.chat_id = chats_expanded.id
+ AND current_lease.runner_id = chats_expanded.runner_id
+ AND current_lease.heartbeat_at > NOW() - (INTERVAL '1 second' * $1::int)
+ ) AS heartbeat_stale
+FROM chats_expanded
+LEFT JOIN chat_heartbeats
+ ON chat_heartbeats.chat_id = chats_expanded.id
+ AND chat_heartbeats.runner_id = chats_expanded.runner_id
+WHERE
+ chats_expanded.status IN ('running'::chat_status, 'interrupting'::chat_status, 'requires_action'::chat_status)
+ AND chats_expanded.archived = false
+ AND (
+ chats_expanded.worker_id IS NULL
+ OR chats_expanded.runner_id IS NULL
+ OR NOT EXISTS (
+ SELECT 1
+ FROM chat_heartbeats current_lease
+ WHERE current_lease.chat_id = chats_expanded.id
+ AND current_lease.runner_id = chats_expanded.runner_id
+ AND current_lease.heartbeat_at > NOW() - (INTERVAL '1 second' * $1::int)
+ )
+ )
+ORDER BY chats_expanded.updated_at ASC, chats_expanded.id ASC
+LIMIT $2::int
+`
+
+type GetChatWorkerAcquisitionCandidatesParams struct {
+ StaleSeconds int32 `db:"stale_seconds" json:"stale_seconds"`
+ LimitCount int32 `db:"limit_count" json:"limit_count"`
+}
+
+type GetChatWorkerAcquisitionCandidatesRow 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"`
+ SnapshotVersion int64 `db:"snapshot_version" json:"snapshot_version"`
+ HistoryVersion int64 `db:"history_version" json:"history_version"`
+ QueueVersion int64 `db:"queue_version" json:"queue_version"`
+ GenerationAttempt int64 `db:"generation_attempt" json:"generation_attempt"`
+ RetryState pqtype.NullRawMessage `db:"retry_state" json:"retry_state"`
+ RetryStateVersion int64 `db:"retry_state_version" json:"retry_state_version"`
+ RunnerID uuid.NullUUID `db:"runner_id" json:"runner_id"`
+ RequiresActionDeadlineAt sql.NullTime `db:"requires_action_deadline_at" json:"requires_action_deadline_at"`
+ UserACL ChatACL `db:"user_acl" json:"user_acl"`
+ GroupACL ChatACL `db:"group_acl" json:"group_acl"`
+ OwnerUsername string `db:"owner_username" json:"owner_username"`
+ OwnerName string `db:"owner_name" json:"owner_name"`
+ CurrentHeartbeatAt sql.NullTime `db:"current_heartbeat_at" json:"current_heartbeat_at"`
+ HeartbeatStale bool `db:"heartbeat_stale" json:"heartbeat_stale"`
+}
+
+// Returns worker-runnable chats whose ownership is missing or whose
+// current runner heartbeat is stale. The runner_id IS NULL predicate is
+// a robustness extension for inconsistent rows where a worker_id exists
+// without a runner_id; normal missing ownership is worker_id IS NULL or
+// a missing or stale heartbeat row.
+func (q *sqlQuerier) GetChatWorkerAcquisitionCandidates(ctx context.Context, arg GetChatWorkerAcquisitionCandidatesParams) ([]GetChatWorkerAcquisitionCandidatesRow, error) {
+ rows, err := q.db.QueryContext(ctx, getChatWorkerAcquisitionCandidates, arg.StaleSeconds, arg.LimitCount)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []GetChatWorkerAcquisitionCandidatesRow
+ for rows.Next() {
+ var i GetChatWorkerAcquisitionCandidatesRow
+ if err := rows.Scan(
+ &i.ID,
+ &i.OwnerID,
+ &i.WorkspaceID,
+ &i.Title,
+ &i.Status,
+ &i.WorkerID,
+ &i.StartedAt,
+ &i.HeartbeatAt,
+ &i.CreatedAt,
+ &i.UpdatedAt,
+ &i.ParentChatID,
+ &i.RootChatID,
+ &i.LastModelConfigID,
+ &i.Archived,
+ &i.LastError,
+ &i.Mode,
+ pq.Array(&i.MCPServerIDs),
+ &i.Labels,
+ &i.BuildID,
+ &i.AgentID,
+ &i.PinOrder,
+ &i.LastReadMessageID,
+ &i.LastInjectedContext,
+ &i.DynamicTools,
+ &i.OrganizationID,
+ &i.PlanMode,
+ &i.ClientType,
+ &i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
+ &i.UserACL,
+ &i.GroupACL,
+ &i.OwnerUsername,
+ &i.OwnerName,
+ &i.CurrentHeartbeatAt,
+ &i.HeartbeatStale,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Close(); err != nil {
+ return nil, err
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
const getChats = `-- name: GetChats :many
WITH cursor_chat AS (
SELECT
@@ -7776,7 +8332,7 @@ WITH cursor_chat AS (
WHERE id = $5
)
SELECT
- 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.user_acl, chats_expanded.group_acl, chats_expanded.owner_username, chats_expanded.owner_name,
+ 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.snapshot_version, chats_expanded.history_version, chats_expanded.queue_version, chats_expanded.generation_attempt, chats_expanded.retry_state, chats_expanded.retry_state_version, chats_expanded.runner_id, chats_expanded.requires_action_deadline_at, chats_expanded.user_acl, chats_expanded.group_acl, chats_expanded.owner_username, chats_expanded.owner_name,
EXISTS (
SELECT 1 FROM chat_messages cm
WHERE cm.chat_id = chats_expanded.id
@@ -8008,6 +8564,14 @@ func (q *sqlQuerier) GetChats(ctx context.Context, arg GetChatsParams) ([]GetCha
&i.Chat.PlanMode,
&i.Chat.ClientType,
&i.Chat.LastTurnSummary,
+ &i.Chat.SnapshotVersion,
+ &i.Chat.HistoryVersion,
+ &i.Chat.QueueVersion,
+ &i.Chat.GenerationAttempt,
+ &i.Chat.RetryState,
+ &i.Chat.RetryStateVersion,
+ &i.Chat.RunnerID,
+ &i.Chat.RequiresActionDeadlineAt,
&i.Chat.UserACL,
&i.Chat.GroupACL,
&i.Chat.OwnerUsername,
@@ -8029,7 +8593,7 @@ func (q *sqlQuerier) GetChats(ctx context.Context, arg GetChatsParams) ([]GetCha
const getChatsByChatFileID = `-- name: GetChatsByChatFileID :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, user_acl, group_acl, owner_username, owner_name
+ 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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM
chats_expanded
WHERE
@@ -8080,6 +8644,85 @@ func (q *sqlQuerier) GetChatsByChatFileID(ctx context.Context, fileID uuid.UUID)
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
+ &i.UserACL,
+ &i.GroupACL,
+ &i.OwnerUsername,
+ &i.OwnerName,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Close(); err != nil {
+ return nil, err
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
+const getChatsByIDsForRunnerSync = `-- name: GetChatsByIDsForRunnerSync :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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
+FROM chats_expanded
+WHERE id = ANY($1::uuid[])
+ORDER BY id ASC
+`
+
+func (q *sqlQuerier) GetChatsByIDsForRunnerSync(ctx context.Context, ids []uuid.UUID) ([]Chat, error) {
+ rows, err := q.db.QueryContext(ctx, getChatsByIDsForRunnerSync, pq.Array(ids))
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []Chat
+ for rows.Next() {
+ var i Chat
+ if err := rows.Scan(
+ &i.ID,
+ &i.OwnerID,
+ &i.WorkspaceID,
+ &i.Title,
+ &i.Status,
+ &i.WorkerID,
+ &i.StartedAt,
+ &i.HeartbeatAt,
+ &i.CreatedAt,
+ &i.UpdatedAt,
+ &i.ParentChatID,
+ &i.RootChatID,
+ &i.LastModelConfigID,
+ &i.Archived,
+ &i.LastError,
+ &i.Mode,
+ pq.Array(&i.MCPServerIDs),
+ &i.Labels,
+ &i.BuildID,
+ &i.AgentID,
+ &i.PinOrder,
+ &i.LastReadMessageID,
+ &i.LastInjectedContext,
+ &i.DynamicTools,
+ &i.OrganizationID,
+ &i.PlanMode,
+ &i.ClientType,
+ &i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -8099,7 +8742,7 @@ func (q *sqlQuerier) GetChatsByChatFileID(ctx context.Context, fileID uuid.UUID)
}
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
WHERE archived = false
AND workspace_id = ANY($1::uuid[])
@@ -8144,6 +8787,14 @@ func (q *sqlQuerier) GetChatsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -8232,7 +8883,7 @@ func (q *sqlQuerier) GetChatsUpdatedAfter(ctx context.Context, updatedAfter time
const getChildChatsByParentIDs = `-- name: GetChildChatsByParentIDs :many
SELECT
- 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.user_acl, chats_expanded.group_acl, chats_expanded.owner_username, chats_expanded.owner_name,
+ 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.snapshot_version, chats_expanded.history_version, chats_expanded.queue_version, chats_expanded.generation_attempt, chats_expanded.retry_state, chats_expanded.retry_state_version, chats_expanded.runner_id, chats_expanded.requires_action_deadline_at, chats_expanded.user_acl, chats_expanded.group_acl, chats_expanded.owner_username, chats_expanded.owner_name,
EXISTS (
SELECT 1 FROM chat_messages cm
WHERE cm.chat_id = chats_expanded.id
@@ -8305,6 +8956,14 @@ func (q *sqlQuerier) GetChildChatsByParentIDs(ctx context.Context, arg GetChildC
&i.Chat.PlanMode,
&i.Chat.ClientType,
&i.Chat.LastTurnSummary,
+ &i.Chat.SnapshotVersion,
+ &i.Chat.HistoryVersion,
+ &i.Chat.QueueVersion,
+ &i.Chat.GenerationAttempt,
+ &i.Chat.RetryState,
+ &i.Chat.RetryStateVersion,
+ &i.Chat.RunnerID,
+ &i.Chat.RequiresActionDeadlineAt,
&i.Chat.UserACL,
&i.Chat.GroupACL,
&i.Chat.OwnerUsername,
@@ -8324,9 +8983,23 @@ func (q *sqlQuerier) GetChildChatsByParentIDs(ctx context.Context, arg GetChildC
return items, nil
}
+const getDatabaseNow = `-- name: GetDatabaseNow :one
+SELECT NOW()::timestamptz AS now
+`
+
+// Returns the current database timestamp. Used so transitions that
+// record deadlines or heartbeats rely on a clock that is consistent
+// with the database rather than the caller's local clock.
+func (q *sqlQuerier) GetDatabaseNow(ctx context.Context) (time.Time, error) {
+ row := q.db.QueryRowContext(ctx, getDatabaseNow)
+ var now time.Time
+ err := row.Scan(&now)
+ return now, err
+}
+
const getLastChatMessageByRole = `-- name: GetLastChatMessageByRole :one
SELECT
- id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id
+ id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id, revision
FROM
chat_messages
WHERE
@@ -8369,13 +9042,14 @@ func (q *sqlQuerier) GetLastChatMessageByRole(ctx context.Context, arg GetLastCh
&i.RuntimeMs,
&i.Deleted,
&i.ProviderResponseID,
+ &i.Revision,
)
return i, err
}
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, user_acl, group_acl, owner_username, owner_name
+ 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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM
chats_expanded
WHERE
@@ -8436,6 +9110,14 @@ func (q *sqlQuerier) GetStaleChats(ctx context.Context, staleThreshold time.Time
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -8516,6 +9198,21 @@ func (q *sqlQuerier) GetUserGroupSpendLimit(ctx context.Context, arg GetUserGrou
return limit_micros, err
}
+const incrementChatGenerationAttempt = `-- name: IncrementChatGenerationAttempt :one
+UPDATE chats
+SET generation_attempt = generation_attempt + 1, updated_at = NOW()
+WHERE id = $1::uuid
+RETURNING generation_attempt
+`
+
+// Increments generation_attempt and returns the resulting value.
+func (q *sqlQuerier) IncrementChatGenerationAttempt(ctx context.Context, id uuid.UUID) (int64, error) {
+ row := q.db.QueryRowContext(ctx, incrementChatGenerationAttempt, id)
+ var generation_attempt int64
+ err := row.Scan(&generation_attempt)
+ return generation_attempt, err
+}
+
const insertChat = `-- name: InsertChat :one
WITH inserted_chat AS (
INSERT INTO chats (
@@ -8553,7 +9250,7 @@ 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, user_acl, group_acl
+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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
),
chats_expanded AS (
SELECT
@@ -8585,6 +9282,14 @@ chats_expanded AS (
inserted_chat.plan_mode,
inserted_chat.client_type,
inserted_chat.last_turn_summary,
+ inserted_chat.snapshot_version,
+ inserted_chat.history_version,
+ inserted_chat.queue_version,
+ inserted_chat.generation_attempt,
+ inserted_chat.retry_state,
+ inserted_chat.retry_state_version,
+ inserted_chat.runner_id,
+ inserted_chat.requires_action_deadline_at,
COALESCE(root.user_acl, inserted_chat.user_acl) AS user_acl,
COALESCE(root.group_acl, inserted_chat.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -8594,7 +9299,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(inserted_chat.root_chat_id, inserted_chat.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
`
@@ -8666,6 +9371,14 @@ func (q *sqlQuerier) InsertChat(ctx context.Context, arg InsertChatParams) (Chat
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -8743,7 +9456,7 @@ SELECT
NULLIF(UNNEST($17::bigint[]), 0),
NULLIF(UNNEST($18::text[]), '')
RETURNING
- id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id
+ id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id, revision
`
type InsertChatMessagesParams struct {
@@ -8817,6 +9530,7 @@ func (q *sqlQuerier) InsertChatMessages(ctx context.Context, arg InsertChatMessa
&i.RuntimeMs,
&i.Deleted,
&i.ProviderResponseID,
+ &i.Revision,
); err != nil {
return nil, err
}
@@ -8832,13 +9546,15 @@ func (q *sqlQuerier) InsertChatMessages(ctx context.Context, arg InsertChatMessa
}
const insertChatQueuedMessage = `-- name: InsertChatQueuedMessage :one
-INSERT INTO chat_queued_messages (chat_id, content, model_config_id)
-VALUES (
- $1,
- $2,
- $3::uuid
-)
-RETURNING id, chat_id, content, created_at, model_config_id
+INSERT INTO chat_queued_messages (chat_id, content, model_config_id, created_by)
+SELECT
+ $1::uuid,
+ $2::jsonb,
+ $3::uuid,
+ chats.owner_id
+FROM chats
+WHERE chats.id = $1::uuid
+RETURNING id, chat_id, content, created_at, model_config_id, position, created_by
`
type InsertChatQueuedMessageParams struct {
@@ -8847,6 +9563,9 @@ type InsertChatQueuedMessageParams struct {
ModelConfigID uuid.NullUUID `db:"model_config_id" json:"model_config_id"`
}
+// Legacy queue insertion path. When no caller-supplied creator exists,
+// preserve the created_by invariant by attributing the queued row to the
+// chat owner.
func (q *sqlQuerier) InsertChatQueuedMessage(ctx context.Context, arg InsertChatQueuedMessageParams) (ChatQueuedMessage, error) {
row := q.db.QueryRowContext(ctx, insertChatQueuedMessage, arg.ChatID, arg.Content, arg.ModelConfigID)
var i ChatQueuedMessage
@@ -8856,10 +9575,79 @@ func (q *sqlQuerier) InsertChatQueuedMessage(ctx context.Context, arg InsertChat
&i.Content,
&i.CreatedAt,
&i.ModelConfigID,
+ &i.Position,
+ &i.CreatedBy,
)
return i, err
}
+const insertChatQueuedMessageWithCreator = `-- name: InsertChatQueuedMessageWithCreator :one
+INSERT INTO chat_queued_messages (chat_id, content, model_config_id, created_by)
+VALUES (
+ $1::uuid,
+ $2::jsonb,
+ $3::uuid,
+ $4::uuid
+)
+RETURNING id, chat_id, content, created_at, model_config_id, position, created_by
+`
+
+type InsertChatQueuedMessageWithCreatorParams struct {
+ ChatID uuid.UUID `db:"chat_id" json:"chat_id"`
+ Content json.RawMessage `db:"content" json:"content"`
+ ModelConfigID uuid.NullUUID `db:"model_config_id" json:"model_config_id"`
+ CreatedBy uuid.UUID `db:"created_by" json:"created_by"`
+}
+
+// Inserts a queued message that carries a position (from the default
+// sequence) and an explicit created_by reference. Use this when the
+// queued-message creator differs from the chat owner.
+func (q *sqlQuerier) InsertChatQueuedMessageWithCreator(ctx context.Context, arg InsertChatQueuedMessageWithCreatorParams) (ChatQueuedMessage, error) {
+ row := q.db.QueryRowContext(ctx, insertChatQueuedMessageWithCreator,
+ arg.ChatID,
+ arg.Content,
+ arg.ModelConfigID,
+ arg.CreatedBy,
+ )
+ var i ChatQueuedMessage
+ err := row.Scan(
+ &i.ID,
+ &i.ChatID,
+ &i.Content,
+ &i.CreatedAt,
+ &i.ModelConfigID,
+ &i.Position,
+ &i.CreatedBy,
+ )
+ return i, err
+}
+
+const isChatHeartbeatStale = `-- name: IsChatHeartbeatStale :one
+SELECT NOT EXISTS (
+ SELECT 1 FROM chat_heartbeats
+ WHERE chat_id = $1::uuid
+ AND runner_id = $2::uuid
+ AND heartbeat_at > NOW() - (INTERVAL '1 second' * $3::int)
+) AS stale
+`
+
+type IsChatHeartbeatStaleParams struct {
+ ChatID uuid.UUID `db:"chat_id" json:"chat_id"`
+ RunnerID uuid.UUID `db:"runner_id" json:"runner_id"`
+ StaleSeconds int32 `db:"stale_seconds" json:"stale_seconds"`
+}
+
+// Returns true when there is no heartbeat row for (chat_id, runner_id)
+// or the existing row is older than @stale_seconds seconds by database
+// time. chatstate calls this in a single query so the staleness check
+// is atomic and does not depend on the caller's local clock.
+func (q *sqlQuerier) IsChatHeartbeatStale(ctx context.Context, arg IsChatHeartbeatStaleParams) (bool, error) {
+ row := q.db.QueryRowContext(ctx, isChatHeartbeatStale, arg.ChatID, arg.RunnerID, arg.StaleSeconds)
+ var stale bool
+ err := row.Scan(&stale)
+ return stale, err
+}
+
const linkChatFiles = `-- name: LinkChatFiles :one
WITH current AS (
SELECT COUNT(*) AS cnt
@@ -9011,6 +9799,128 @@ func (q *sqlQuerier) ListChatUsageLimitOverrides(ctx context.Context) ([]ListCha
return items, nil
}
+const lockChatAndBumpSnapshotVersion = `-- name: LockChatAndBumpSnapshotVersion :one
+
+WITH bumped_chat AS (
+ UPDATE chats
+ SET snapshot_version = snapshot_version + 1
+ WHERE id = (
+ SELECT id FROM chats
+ WHERE id = $1::uuid
+ FOR UPDATE
+ )
+ 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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
+),
+chats_expanded AS (
+ SELECT
+ bumped_chat.id,
+ bumped_chat.owner_id,
+ bumped_chat.workspace_id,
+ bumped_chat.title,
+ bumped_chat.status,
+ bumped_chat.worker_id,
+ bumped_chat.started_at,
+ bumped_chat.heartbeat_at,
+ bumped_chat.created_at,
+ bumped_chat.updated_at,
+ bumped_chat.parent_chat_id,
+ bumped_chat.root_chat_id,
+ bumped_chat.last_model_config_id,
+ bumped_chat.archived,
+ bumped_chat.last_error,
+ bumped_chat.mode,
+ bumped_chat.mcp_server_ids,
+ bumped_chat.labels,
+ bumped_chat.build_id,
+ bumped_chat.agent_id,
+ bumped_chat.pin_order,
+ bumped_chat.last_read_message_id,
+ bumped_chat.last_injected_context,
+ bumped_chat.dynamic_tools,
+ bumped_chat.organization_id,
+ bumped_chat.plan_mode,
+ bumped_chat.client_type,
+ bumped_chat.last_turn_summary,
+ bumped_chat.snapshot_version,
+ bumped_chat.history_version,
+ bumped_chat.queue_version,
+ bumped_chat.generation_attempt,
+ bumped_chat.retry_state,
+ bumped_chat.retry_state_version,
+ bumped_chat.runner_id,
+ bumped_chat.requires_action_deadline_at,
+ COALESCE(root.user_acl, bumped_chat.user_acl) AS user_acl,
+ COALESCE(root.group_acl, bumped_chat.group_acl) AS group_acl,
+ owner.username AS owner_username,
+ owner.name AS owner_name
+ FROM bumped_chat
+ LEFT JOIN chats root ON root.id = COALESCE(bumped_chat.root_chat_id, bumped_chat.parent_chat_id)
+ JOIN visible_users owner ON owner.id = bumped_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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
+FROM chats_expanded
+`
+
+// =====================================================================
+// chatd core state machine queries.
+//
+// These are consumed by the coderd/x/chatd/chatstate package. They
+// are intentionally kept side-by-side with the legacy chatd queries
+// above so the existing runtime keeps working while the state machine
+// lands behind it.
+// =====================================================================
+// Locks the chat row with FOR UPDATE and atomically increments its
+// snapshot_version, returning the post-bump chat. This is the single
+// entry point ChatMachine.Update uses to acquire the row lock and
+// allocate a new snapshot version in one round trip.
+func (q *sqlQuerier) LockChatAndBumpSnapshotVersion(ctx context.Context, id uuid.UUID) (Chat, error) {
+ row := q.db.QueryRowContext(ctx, lockChatAndBumpSnapshotVersion, id)
+ var i Chat
+ err := row.Scan(
+ &i.ID,
+ &i.OwnerID,
+ &i.WorkspaceID,
+ &i.Title,
+ &i.Status,
+ &i.WorkerID,
+ &i.StartedAt,
+ &i.HeartbeatAt,
+ &i.CreatedAt,
+ &i.UpdatedAt,
+ &i.ParentChatID,
+ &i.RootChatID,
+ &i.LastModelConfigID,
+ &i.Archived,
+ &i.LastError,
+ &i.Mode,
+ pq.Array(&i.MCPServerIDs),
+ &i.Labels,
+ &i.BuildID,
+ &i.AgentID,
+ &i.PinOrder,
+ &i.LastReadMessageID,
+ &i.LastInjectedContext,
+ &i.DynamicTools,
+ &i.OrganizationID,
+ &i.PlanMode,
+ &i.ClientType,
+ &i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
+ &i.UserACL,
+ &i.GroupACL,
+ &i.OwnerUsername,
+ &i.OwnerName,
+ )
+ return i, err
+}
+
const pinChatByID = `-- name: PinChatByID :exec
WITH target_chat AS (
SELECT
@@ -9080,7 +9990,7 @@ WHERE id = (
ORDER BY cqm.created_at ASC, cqm.id ASC
LIMIT 1
)
-RETURNING id, chat_id, content, created_at, model_config_id
+RETURNING id, chat_id, content, created_at, model_config_id, position, created_by
`
func (q *sqlQuerier) PopNextQueuedMessage(ctx context.Context, chatID uuid.UUID) (ChatQueuedMessage, error) {
@@ -9092,6 +10002,8 @@ func (q *sqlQuerier) PopNextQueuedMessage(ctx context.Context, chatID uuid.UUID)
&i.Content,
&i.CreatedAt,
&i.ModelConfigID,
+ &i.Position,
+ &i.CreatedBy,
)
return i, err
}
@@ -9121,6 +10033,35 @@ func (q *sqlQuerier) ReorderChatQueuedMessageToFront(ctx context.Context, arg Re
return result.RowsAffected()
}
+const reorderChatQueuedMessageToHead = `-- name: ReorderChatQueuedMessageToHead :execrows
+UPDATE chat_queued_messages AS target
+SET position = COALESCE(
+ (SELECT MIN(position) FROM chat_queued_messages WHERE chat_id = $1::uuid),
+ 0
+) - 1
+WHERE target.id = $2::bigint
+ AND target.chat_id = $1::uuid
+ AND target.position > COALESCE(
+ (SELECT MIN(position) FROM chat_queued_messages WHERE chat_id = $1::uuid),
+ target.position
+ )
+`
+
+type ReorderChatQueuedMessageToHeadParams struct {
+ ChatID uuid.UUID `db:"chat_id" json:"chat_id"`
+ ID int64 `db:"id" json:"id"`
+}
+
+// Sets the target queued message's position to one less than the
+// current minimum position for that chat, moving it to the head.
+func (q *sqlQuerier) ReorderChatQueuedMessageToHead(ctx context.Context, arg ReorderChatQueuedMessageToHeadParams) (int64, error) {
+ result, err := q.db.ExecContext(ctx, reorderChatQueuedMessageToHead, arg.ChatID, arg.ID)
+ if err != nil {
+ return 0, err
+ }
+ return result.RowsAffected()
+}
+
const resolveUserChatSpendLimit = `-- name: ResolveUserChatSpendLimit :one
SELECT CASE
WHEN NOT cfg.enabled THEN -1
@@ -9230,7 +10171,7 @@ WITH updated_chats AS (
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, user_acl, group_acl
+ 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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
),
chats_expanded AS (
SELECT
@@ -9262,6 +10203,14 @@ chats_expanded AS (
updated_chats.plan_mode,
updated_chats.client_type,
updated_chats.last_turn_summary,
+ updated_chats.snapshot_version,
+ updated_chats.history_version,
+ updated_chats.queue_version,
+ updated_chats.generation_attempt,
+ updated_chats.retry_state,
+ updated_chats.retry_state_version,
+ updated_chats.runner_id,
+ updated_chats.requires_action_deadline_at,
COALESCE(root.user_acl, updated_chats.user_acl) AS user_acl,
COALESCE(root.group_acl, updated_chats.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -9271,7 +10220,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(updated_chats.root_chat_id, updated_chats.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
ORDER BY (chats_expanded.id = $1::uuid) DESC, chats_expanded.created_at ASC, chats_expanded.id ASC
`
@@ -9318,6 +10267,14 @@ func (q *sqlQuerier) UnarchiveChatByID(ctx context.Context, id uuid.UUID) ([]Cha
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -9424,7 +10381,7 @@ UPDATE chats SET
updated_at = NOW()
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, user_acl, group_acl
+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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
),
chats_expanded AS (
SELECT
@@ -9456,6 +10413,14 @@ chats_expanded AS (
updated_chat.plan_mode,
updated_chat.client_type,
updated_chat.last_turn_summary,
+ updated_chat.snapshot_version,
+ updated_chat.history_version,
+ updated_chat.queue_version,
+ updated_chat.generation_attempt,
+ updated_chat.retry_state,
+ updated_chat.retry_state_version,
+ updated_chat.runner_id,
+ updated_chat.requires_action_deadline_at,
COALESCE(root.user_acl, updated_chat.user_acl) AS user_acl,
COALESCE(root.group_acl, updated_chat.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -9465,7 +10430,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(updated_chat.root_chat_id, updated_chat.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
`
@@ -9507,6 +10472,14 @@ func (q *sqlQuerier) UpdateChatBuildAgentBinding(ctx context.Context, arg Update
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -9524,7 +10497,7 @@ 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, user_acl, group_acl
+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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
),
chats_expanded AS (
SELECT
@@ -9556,6 +10529,14 @@ chats_expanded AS (
updated_chat.plan_mode,
updated_chat.client_type,
updated_chat.last_turn_summary,
+ updated_chat.snapshot_version,
+ updated_chat.history_version,
+ updated_chat.queue_version,
+ updated_chat.generation_attempt,
+ updated_chat.retry_state,
+ updated_chat.retry_state_version,
+ updated_chat.runner_id,
+ updated_chat.requires_action_deadline_at,
COALESCE(root.user_acl, updated_chat.user_acl) AS user_acl,
COALESCE(root.group_acl, updated_chat.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -9565,7 +10546,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(updated_chat.root_chat_id, updated_chat.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
`
@@ -9606,6 +10587,149 @@ func (q *sqlQuerier) UpdateChatByID(ctx context.Context, arg UpdateChatByIDParam
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
+ &i.UserACL,
+ &i.GroupACL,
+ &i.OwnerUsername,
+ &i.OwnerName,
+ )
+ return i, err
+}
+
+const updateChatExecutionState = `-- name: UpdateChatExecutionState :one
+WITH updated_chat AS (
+ UPDATE chats
+ SET
+ status = $1::chat_status,
+ archived = $2::boolean,
+ worker_id = $3::uuid,
+ runner_id = $4::uuid,
+ last_error = $5::jsonb,
+ requires_action_deadline_at = $6::timestamptz,
+ pin_order = CASE WHEN $2::boolean THEN 0 ELSE pin_order END,
+ updated_at = NOW()
+ 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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
+),
+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,
+ updated_chat.snapshot_version,
+ updated_chat.history_version,
+ updated_chat.queue_version,
+ updated_chat.generation_attempt,
+ updated_chat.retry_state,
+ updated_chat.retry_state_version,
+ updated_chat.runner_id,
+ updated_chat.requires_action_deadline_at,
+ COALESCE(root.user_acl, updated_chat.user_acl) AS user_acl,
+ COALESCE(root.group_acl, updated_chat.group_acl) AS group_acl,
+ owner.username AS owner_username,
+ owner.name AS owner_name
+ FROM updated_chat
+ LEFT JOIN chats root ON root.id = COALESCE(updated_chat.root_chat_id, updated_chat.parent_chat_id)
+ 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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
+FROM chats_expanded
+`
+
+type UpdateChatExecutionStateParams struct {
+ Status ChatStatus `db:"status" json:"status"`
+ Archived bool `db:"archived" json:"archived"`
+ WorkerID uuid.NullUUID `db:"worker_id" json:"worker_id"`
+ RunnerID uuid.NullUUID `db:"runner_id" json:"runner_id"`
+ LastError pqtype.NullRawMessage `db:"last_error" json:"last_error"`
+ RequiresActionDeadlineAt sql.NullTime `db:"requires_action_deadline_at" json:"requires_action_deadline_at"`
+ ID uuid.UUID `db:"id" json:"id"`
+}
+
+// Atomically updates the execution-state-managed fields on a chat:
+// status, archived, last_error, ownership identifiers, and the
+// requires-action deadline. Callers compose this with transition
+// mutations inside a single ChatMachine.Update transaction.
+func (q *sqlQuerier) UpdateChatExecutionState(ctx context.Context, arg UpdateChatExecutionStateParams) (Chat, error) {
+ row := q.db.QueryRowContext(ctx, updateChatExecutionState,
+ arg.Status,
+ arg.Archived,
+ arg.WorkerID,
+ arg.RunnerID,
+ arg.LastError,
+ arg.RequiresActionDeadlineAt,
+ arg.ID,
+ )
+ var i Chat
+ err := row.Scan(
+ &i.ID,
+ &i.OwnerID,
+ &i.WorkspaceID,
+ &i.Title,
+ &i.Status,
+ &i.WorkerID,
+ &i.StartedAt,
+ &i.HeartbeatAt,
+ &i.CreatedAt,
+ &i.UpdatedAt,
+ &i.ParentChatID,
+ &i.RootChatID,
+ &i.LastModelConfigID,
+ &i.Archived,
+ &i.LastError,
+ &i.Mode,
+ pq.Array(&i.MCPServerIDs),
+ &i.Labels,
+ &i.BuildID,
+ &i.AgentID,
+ &i.PinOrder,
+ &i.LastReadMessageID,
+ &i.LastInjectedContext,
+ &i.DynamicTools,
+ &i.OrganizationID,
+ &i.PlanMode,
+ &i.ClientType,
+ &i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -9668,7 +10792,7 @@ 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, user_acl, group_acl
+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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
),
chats_expanded AS (
SELECT
@@ -9700,6 +10824,14 @@ chats_expanded AS (
updated_chat.plan_mode,
updated_chat.client_type,
updated_chat.last_turn_summary,
+ updated_chat.snapshot_version,
+ updated_chat.history_version,
+ updated_chat.queue_version,
+ updated_chat.generation_attempt,
+ updated_chat.retry_state,
+ updated_chat.retry_state_version,
+ updated_chat.runner_id,
+ updated_chat.requires_action_deadline_at,
COALESCE(root.user_acl, updated_chat.user_acl) AS user_acl,
COALESCE(root.group_acl, updated_chat.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -9709,7 +10841,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(updated_chat.root_chat_id, updated_chat.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
`
@@ -9750,6 +10882,14 @@ func (q *sqlQuerier) UpdateChatLabelsByID(ctx context.Context, arg UpdateChatLab
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -9764,7 +10904,7 @@ 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, user_acl, group_acl
+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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
),
chats_expanded AS (
SELECT
@@ -9796,6 +10936,14 @@ chats_expanded AS (
updated_chat.plan_mode,
updated_chat.client_type,
updated_chat.last_turn_summary,
+ updated_chat.snapshot_version,
+ updated_chat.history_version,
+ updated_chat.queue_version,
+ updated_chat.generation_attempt,
+ updated_chat.retry_state,
+ updated_chat.retry_state_version,
+ updated_chat.runner_id,
+ updated_chat.requires_action_deadline_at,
COALESCE(root.user_acl, updated_chat.user_acl) AS user_acl,
COALESCE(root.group_acl, updated_chat.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -9805,7 +10953,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(updated_chat.root_chat_id, updated_chat.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
`
@@ -9850,6 +10998,14 @@ func (q *sqlQuerier) UpdateChatLastInjectedContext(ctx context.Context, arg Upda
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -9867,7 +11023,7 @@ 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, user_acl, group_acl
+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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
),
chats_expanded AS (
SELECT
@@ -9899,6 +11055,14 @@ chats_expanded AS (
updated_chat.plan_mode,
updated_chat.client_type,
updated_chat.last_turn_summary,
+ updated_chat.snapshot_version,
+ updated_chat.history_version,
+ updated_chat.queue_version,
+ updated_chat.generation_attempt,
+ updated_chat.retry_state,
+ updated_chat.retry_state_version,
+ updated_chat.runner_id,
+ updated_chat.requires_action_deadline_at,
COALESCE(root.user_acl, updated_chat.user_acl) AS user_acl,
COALESCE(root.group_acl, updated_chat.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -9908,7 +11072,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(updated_chat.root_chat_id, updated_chat.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
`
@@ -9949,6 +11113,14 @@ func (q *sqlQuerier) UpdateChatLastModelConfigByID(ctx context.Context, arg Upda
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -9983,26 +11155,24 @@ SET
), '')
WHERE
id = $2::uuid
- AND updated_at = $3::timestamptz
+ AND history_version = $3::bigint
`
type UpdateChatLastTurnSummaryParams struct {
- LastTurnSummary sql.NullString `db:"last_turn_summary" json:"last_turn_summary"`
- ID uuid.UUID `db:"id" json:"id"`
- ExpectedUpdatedAt time.Time `db:"expected_updated_at" json:"expected_updated_at"`
+ LastTurnSummary sql.NullString `db:"last_turn_summary" json:"last_turn_summary"`
+ ID uuid.UUID `db:"id" json:"id"`
+ ExpectedHistoryVersion int64 `db:"expected_history_version" json:"expected_history_version"`
}
// Updates the cached last completed turn summary for sidebar display.
// Empty or whitespace-only summaries are stored as NULL here so direct
// query callers cannot accidentally persist blank sidebar text.
-// This intentionally preserves updated_at. The staleness guard relies on
-// every new-turn query, such as UpdateChatStatus and AcquireChats, bumping
-// updated_at. Future chat-field updates that do not bump updated_at can let
-// stale summaries persist. If this query ever bumps updated_at, later
-// goroutine summary writes will be rejected as stale.
+// This intentionally preserves updated_at. The staleness guard uses
+// history_version so worker lifecycle transitions that do not change the
+// active message history cannot reject final turn summary writes.
// Two summary workers using the same freshness marker are last-write-wins.
func (q *sqlQuerier) UpdateChatLastTurnSummary(ctx context.Context, arg UpdateChatLastTurnSummaryParams) (int64, error) {
- result, err := q.db.ExecContext(ctx, updateChatLastTurnSummary, arg.LastTurnSummary, arg.ID, arg.ExpectedUpdatedAt)
+ result, err := q.db.ExecContext(ctx, updateChatLastTurnSummary, arg.LastTurnSummary, arg.ID, arg.ExpectedHistoryVersion)
if err != nil {
return 0, err
}
@@ -10018,7 +11188,7 @@ 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, user_acl, group_acl
+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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
),
chats_expanded AS (
SELECT
@@ -10050,6 +11220,14 @@ chats_expanded AS (
updated_chat.plan_mode,
updated_chat.client_type,
updated_chat.last_turn_summary,
+ updated_chat.snapshot_version,
+ updated_chat.history_version,
+ updated_chat.queue_version,
+ updated_chat.generation_attempt,
+ updated_chat.retry_state,
+ updated_chat.retry_state_version,
+ updated_chat.runner_id,
+ updated_chat.requires_action_deadline_at,
COALESCE(root.user_acl, updated_chat.user_acl) AS user_acl,
COALESCE(root.group_acl, updated_chat.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -10059,7 +11237,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(updated_chat.root_chat_id, updated_chat.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
`
@@ -10100,6 +11278,14 @@ func (q *sqlQuerier) UpdateChatMCPServerIDs(ctx context.Context, arg UpdateChatM
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -10117,7 +11303,7 @@ SET
WHERE
id = $3::bigint
RETURNING
- id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id
+ id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms, deleted, provider_response_id, revision
`
type UpdateChatMessageByIDParams struct {
@@ -10151,6 +11337,7 @@ func (q *sqlQuerier) UpdateChatMessageByID(ctx context.Context, arg UpdateChatMe
&i.RuntimeMs,
&i.Deleted,
&i.ProviderResponseID,
+ &i.Revision,
)
return i, err
}
@@ -10235,7 +11422,7 @@ 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, user_acl, group_acl
+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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
),
chats_expanded AS (
SELECT
@@ -10267,6 +11454,14 @@ chats_expanded AS (
updated_chat.plan_mode,
updated_chat.client_type,
updated_chat.last_turn_summary,
+ updated_chat.snapshot_version,
+ updated_chat.history_version,
+ updated_chat.queue_version,
+ updated_chat.generation_attempt,
+ updated_chat.retry_state,
+ updated_chat.retry_state_version,
+ updated_chat.runner_id,
+ updated_chat.requires_action_deadline_at,
COALESCE(root.user_acl, updated_chat.user_acl) AS user_acl,
COALESCE(root.group_acl, updated_chat.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -10276,7 +11471,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(updated_chat.root_chat_id, updated_chat.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
`
@@ -10317,6 +11512,14 @@ func (q *sqlQuerier) UpdateChatPlanModeByID(ctx context.Context, arg UpdateChatP
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -10325,20 +11528,14 @@ func (q *sqlQuerier) UpdateChatPlanModeByID(ctx context.Context, arg UpdateChatP
return i, err
}
-const updateChatStatus = `-- name: UpdateChatStatus :one
+const updateChatRetryState = `-- name: UpdateChatRetryState :one
WITH updated_chat AS (
-UPDATE
- chats
-SET
- status = $1::chat_status,
- worker_id = $2::uuid,
- started_at = $3::timestamptz,
- heartbeat_at = $4::timestamptz,
- last_error = $5::jsonb,
- 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, user_acl, group_acl
+ UPDATE chats
+ SET
+ retry_state = $1::jsonb,
+ 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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
),
chats_expanded AS (
SELECT
@@ -10370,6 +11567,134 @@ chats_expanded AS (
updated_chat.plan_mode,
updated_chat.client_type,
updated_chat.last_turn_summary,
+ updated_chat.snapshot_version,
+ updated_chat.history_version,
+ updated_chat.queue_version,
+ updated_chat.generation_attempt,
+ updated_chat.retry_state,
+ updated_chat.retry_state_version,
+ updated_chat.runner_id,
+ updated_chat.requires_action_deadline_at,
+ COALESCE(root.user_acl, updated_chat.user_acl) AS user_acl,
+ COALESCE(root.group_acl, updated_chat.group_acl) AS group_acl,
+ owner.username AS owner_username,
+ owner.name AS owner_name
+ FROM updated_chat
+ LEFT JOIN chats root ON root.id = COALESCE(updated_chat.root_chat_id, updated_chat.parent_chat_id)
+ 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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
+FROM chats_expanded
+`
+
+type UpdateChatRetryStateParams struct {
+ RetryState json.RawMessage `db:"retry_state" json:"retry_state"`
+ ID uuid.UUID `db:"id" json:"id"`
+}
+
+// Stores the client-visible retry payload. retry_state_version is
+// assigned by trigger from the current snapshot_version.
+func (q *sqlQuerier) UpdateChatRetryState(ctx context.Context, arg UpdateChatRetryStateParams) (Chat, error) {
+ row := q.db.QueryRowContext(ctx, updateChatRetryState, arg.RetryState, arg.ID)
+ var i Chat
+ err := row.Scan(
+ &i.ID,
+ &i.OwnerID,
+ &i.WorkspaceID,
+ &i.Title,
+ &i.Status,
+ &i.WorkerID,
+ &i.StartedAt,
+ &i.HeartbeatAt,
+ &i.CreatedAt,
+ &i.UpdatedAt,
+ &i.ParentChatID,
+ &i.RootChatID,
+ &i.LastModelConfigID,
+ &i.Archived,
+ &i.LastError,
+ &i.Mode,
+ pq.Array(&i.MCPServerIDs),
+ &i.Labels,
+ &i.BuildID,
+ &i.AgentID,
+ &i.PinOrder,
+ &i.LastReadMessageID,
+ &i.LastInjectedContext,
+ &i.DynamicTools,
+ &i.OrganizationID,
+ &i.PlanMode,
+ &i.ClientType,
+ &i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
+ &i.UserACL,
+ &i.GroupACL,
+ &i.OwnerUsername,
+ &i.OwnerName,
+ )
+ return i, err
+}
+
+const updateChatStatus = `-- name: UpdateChatStatus :one
+WITH updated_chat AS (
+UPDATE
+ chats
+SET
+ status = $1::chat_status,
+ worker_id = $2::uuid,
+ started_at = $3::timestamptz,
+ heartbeat_at = $4::timestamptz,
+ last_error = $5::jsonb,
+ 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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
+),
+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,
+ updated_chat.snapshot_version,
+ updated_chat.history_version,
+ updated_chat.queue_version,
+ updated_chat.generation_attempt,
+ updated_chat.retry_state,
+ updated_chat.retry_state_version,
+ updated_chat.runner_id,
+ updated_chat.requires_action_deadline_at,
COALESCE(root.user_acl, updated_chat.user_acl) AS user_acl,
COALESCE(root.group_acl, updated_chat.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -10379,7 +11704,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(updated_chat.root_chat_id, updated_chat.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
`
@@ -10431,6 +11756,14 @@ func (q *sqlQuerier) UpdateChatStatus(ctx context.Context, arg UpdateChatStatusP
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -10452,7 +11785,7 @@ 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, user_acl, group_acl
+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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
),
chats_expanded AS (
SELECT
@@ -10484,6 +11817,14 @@ chats_expanded AS (
updated_chat.plan_mode,
updated_chat.client_type,
updated_chat.last_turn_summary,
+ updated_chat.snapshot_version,
+ updated_chat.history_version,
+ updated_chat.queue_version,
+ updated_chat.generation_attempt,
+ updated_chat.retry_state,
+ updated_chat.retry_state_version,
+ updated_chat.runner_id,
+ updated_chat.requires_action_deadline_at,
COALESCE(root.user_acl, updated_chat.user_acl) AS user_acl,
COALESCE(root.group_acl, updated_chat.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -10493,7 +11834,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(updated_chat.root_chat_id, updated_chat.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
`
@@ -10547,6 +11888,14 @@ func (q *sqlQuerier) UpdateChatStatusPreserveUpdatedAt(ctx context.Context, arg
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -10566,7 +11915,7 @@ 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, user_acl, group_acl
+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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
),
chats_expanded AS (
SELECT
@@ -10598,6 +11947,14 @@ chats_expanded AS (
updated_chat.plan_mode,
updated_chat.client_type,
updated_chat.last_turn_summary,
+ updated_chat.snapshot_version,
+ updated_chat.history_version,
+ updated_chat.queue_version,
+ updated_chat.generation_attempt,
+ updated_chat.retry_state,
+ updated_chat.retry_state_version,
+ updated_chat.runner_id,
+ updated_chat.requires_action_deadline_at,
COALESCE(root.user_acl, updated_chat.user_acl) AS user_acl,
COALESCE(root.group_acl, updated_chat.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -10607,7 +11964,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(updated_chat.root_chat_id, updated_chat.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
`
@@ -10648,6 +12005,14 @@ func (q *sqlQuerier) UpdateChatTitleByID(ctx context.Context, arg UpdateChatTitl
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -10664,7 +12029,7 @@ UPDATE chats SET
agent_id = $3::uuid,
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, user_acl, group_acl
+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, user_acl, group_acl, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at
),
chats_expanded AS (
SELECT
@@ -10696,6 +12061,14 @@ chats_expanded AS (
updated_chat.plan_mode,
updated_chat.client_type,
updated_chat.last_turn_summary,
+ updated_chat.snapshot_version,
+ updated_chat.history_version,
+ updated_chat.queue_version,
+ updated_chat.generation_attempt,
+ updated_chat.retry_state,
+ updated_chat.retry_state_version,
+ updated_chat.runner_id,
+ updated_chat.requires_action_deadline_at,
COALESCE(root.user_acl, updated_chat.user_acl) AS user_acl,
COALESCE(root.group_acl, updated_chat.group_acl) AS group_acl,
owner.username AS owner_username,
@@ -10705,7 +12078,7 @@ chats_expanded AS (
LEFT JOIN chats root ON root.id = COALESCE(updated_chat.root_chat_id, updated_chat.parent_chat_id)
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, user_acl, group_acl, owner_username, owner_name
+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, snapshot_version, history_version, queue_version, generation_attempt, retry_state, retry_state_version, runner_id, requires_action_deadline_at, user_acl, group_acl, owner_username, owner_name
FROM chats_expanded
`
@@ -10753,6 +12126,14 @@ func (q *sqlQuerier) UpdateChatWorkspaceBinding(ctx context.Context, arg UpdateC
&i.PlanMode,
&i.ClientType,
&i.LastTurnSummary,
+ &i.SnapshotVersion,
+ &i.HistoryVersion,
+ &i.QueueVersion,
+ &i.GenerationAttempt,
+ &i.RetryState,
+ &i.RetryStateVersion,
+ &i.RunnerID,
+ &i.RequiresActionDeadlineAt,
&i.UserACL,
&i.GroupACL,
&i.OwnerUsername,
@@ -10980,6 +12361,44 @@ func (q *sqlQuerier) UpsertChatDiffStatusReference(ctx context.Context, arg Upse
return i, err
}
+const upsertChatHeartbeat = `-- name: UpsertChatHeartbeat :exec
+INSERT INTO chat_heartbeats (chat_id, runner_id, heartbeat_at)
+VALUES ($1::uuid, $2::uuid, NOW())
+ON CONFLICT (chat_id, runner_id) DO UPDATE
+SET heartbeat_at = EXCLUDED.heartbeat_at
+`
+
+type UpsertChatHeartbeatParams struct {
+ ChatID uuid.UUID `db:"chat_id" json:"chat_id"`
+ RunnerID uuid.UUID `db:"runner_id" json:"runner_id"`
+}
+
+// Upserts a heartbeat row for the (chat_id, runner_id) lease. Uses
+// database time so callers do not depend on a local clock.
+func (q *sqlQuerier) UpsertChatHeartbeat(ctx context.Context, arg UpsertChatHeartbeatParams) error {
+ _, err := q.db.ExecContext(ctx, upsertChatHeartbeat, arg.ChatID, arg.RunnerID)
+ return err
+}
+
+const upsertChatHeartbeats = `-- name: UpsertChatHeartbeats :exec
+INSERT INTO chat_heartbeats (chat_id, runner_id, heartbeat_at)
+SELECT chat_ids.chat_id, runner_ids.runner_id, NOW()
+FROM unnest($1::uuid[]) WITH ORDINALITY AS chat_ids(chat_id, ord)
+JOIN unnest($2::uuid[]) WITH ORDINALITY AS runner_ids(runner_id, ord) USING (ord)
+ON CONFLICT (chat_id, runner_id) DO UPDATE
+SET heartbeat_at = EXCLUDED.heartbeat_at
+`
+
+type UpsertChatHeartbeatsParams struct {
+ ChatIds []uuid.UUID `db:"chat_ids" json:"chat_ids"`
+ RunnerIds []uuid.UUID `db:"runner_ids" json:"runner_ids"`
+}
+
+func (q *sqlQuerier) UpsertChatHeartbeats(ctx context.Context, arg UpsertChatHeartbeatsParams) error {
+ _, err := q.db.ExecContext(ctx, upsertChatHeartbeats, pq.Array(arg.ChatIds), pq.Array(arg.RunnerIds))
+ return err
+}
+
const upsertChatUsageLimitConfig = `-- name: UpsertChatUsageLimitConfig :one
INSERT INTO chat_usage_limit_config (singleton, enabled, default_limit_micros, period, updated_at)
VALUES (TRUE, $1::boolean, $2::bigint, $3::text, NOW())
diff --git a/coderd/database/unique_constraint.go b/coderd/database/unique_constraint.go
index 8ef517a9cb..df2a136598 100644
--- a/coderd/database/unique_constraint.go
+++ b/coderd/database/unique_constraint.go
@@ -25,6 +25,7 @@ const (
UniqueChatDiffStatusesPkey UniqueConstraint = "chat_diff_statuses_pkey" // ALTER TABLE ONLY chat_diff_statuses ADD CONSTRAINT chat_diff_statuses_pkey PRIMARY KEY (chat_id);
UniqueChatFileLinksChatIDFileIDKey UniqueConstraint = "chat_file_links_chat_id_file_id_key" // ALTER TABLE ONLY chat_file_links ADD CONSTRAINT chat_file_links_chat_id_file_id_key UNIQUE (chat_id, file_id);
UniqueChatFilesPkey UniqueConstraint = "chat_files_pkey" // ALTER TABLE ONLY chat_files ADD CONSTRAINT chat_files_pkey PRIMARY KEY (id);
+ UniqueChatHeartbeatsPkey UniqueConstraint = "chat_heartbeats_pkey" // ALTER TABLE ONLY chat_heartbeats ADD CONSTRAINT chat_heartbeats_pkey PRIMARY KEY (chat_id, runner_id);
UniqueChatMessagesPkey UniqueConstraint = "chat_messages_pkey" // ALTER TABLE ONLY chat_messages ADD CONSTRAINT chat_messages_pkey PRIMARY KEY (id);
UniqueChatModelConfigsPkey UniqueConstraint = "chat_model_configs_pkey" // ALTER TABLE ONLY chat_model_configs ADD CONSTRAINT chat_model_configs_pkey PRIMARY KEY (id);
UniqueChatQueuedMessagesPkey UniqueConstraint = "chat_queued_messages_pkey" // ALTER TABLE ONLY chat_queued_messages ADD CONSTRAINT chat_queued_messages_pkey PRIMARY KEY (id);
diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md
index 712724e064..d8c0655709 100644
--- a/docs/admin/security/audit-logs.md
+++ b/docs/admin/security/audit-logs.md
@@ -23,7 +23,7 @@ We track the following resources:
| Group
create, write, delete |
| Field | Tracked |
| | avatar_url | true |
| chat_spend_limit_micros | true |
| display_name | true |
| id | true |
| members | true |
| name | true |
| organization_id | false |
| quota_allowance | true |
| source | false |
|
| AuditableGroupAiBudget
write, delete | | Field | Tracked |
| | created_at | false |
| group_id | false |
| group_name | false |
| spend_limit | true |
| spend_limit_micros | false |
| updated_at | false |
|
| AuditableOrganizationMember
| | Field | Tracked |
| | created_at | true |
| organization_id | false |
| roles | true |
| updated_at | true |
| user_id | true |
| username | true |
|
-| Chat
create, write | | Field | Tracked |
| | agent_id | false |
| archived | true |
| build_id | false |
| client_type | false |
| created_at | false |
| dynamic_tools | false |
| group_acl | true |
| heartbeat_at | false |
| id | true |
| labels | true |
| last_error | false |
| last_injected_context | false |
| last_model_config_id | false |
| last_read_message_id | false |
| last_turn_summary | false |
| mcp_server_ids | true |
| mode | true |
| organization_id | false |
| owner_id | true |
| owner_name | false |
| owner_username | false |
| parent_chat_id | false |
| pin_order | true |
| plan_mode | false |
| root_chat_id | false |
| started_at | false |
| status | false |
| title | true |
| updated_at | false |
| user_acl | true |
| worker_id | false |
| workspace_id | true |
|
+| Chat
create, write | | Field | Tracked |
| | agent_id | false |
| archived | true |
| build_id | false |
| client_type | false |
| created_at | false |
| dynamic_tools | false |
| generation_attempt | false |
| group_acl | true |
| heartbeat_at | false |
| history_version | false |
| id | true |
| labels | true |
| last_error | false |
| last_injected_context | false |
| last_model_config_id | false |
| last_read_message_id | false |
| last_turn_summary | false |
| mcp_server_ids | true |
| mode | true |
| organization_id | false |
| owner_id | true |
| owner_name | false |
| owner_username | false |
| parent_chat_id | false |
| pin_order | true |
| plan_mode | false |
| queue_version | false |
| requires_action_deadline_at | false |
| retry_state | false |
| retry_state_version | false |
| root_chat_id | false |
| runner_id | false |
| snapshot_version | false |
| started_at | false |
| status | false |
| title | true |
| updated_at | false |
| user_acl | true |
| worker_id | false |
| workspace_id | true |
|
| CustomRole
| | Field | Tracked |
| | created_at | false |
| display_name | true |
| id | false |
| is_system | false |
| member_permissions | true |
| name | true |
| org_permissions | true |
| organization_id | false |
| site_permissions | true |
| updated_at | false |
| user_permissions | true |
|
| GitSSHKey
create | | Field | Tracked |
| | created_at | false |
| private_key | true |
| public_key | true |
| updated_at | false |
| user_id | true |
|
| GroupSyncSettings
| | Field | Tracked |
| | auto_create_missing_groups | true |
| field | true |
| legacy_group_name_mapping | false |
| mapping | true |
| regex_filter | true |
|
diff --git a/docs/reference/api/chats.md b/docs/reference/api/chats.md
index 758f6641f5..909cf2b24f 100644
--- a/docs/reference/api/chats.md
+++ b/docs/reference/api/chats.md
@@ -298,7 +298,7 @@ Status Code **200**
| `kind` | `auth`, `config`, `generic`, `overloaded`, `rate_limit`, `startup_timeout`, `timeout`, `usage_limit` |
| `type` | `context-file`, `file`, `file-reference`, `reasoning`, `skill`, `source`, `text`, `tool-call`, `tool-result` |
| `plan_mode` | `plan` |
-| `status` | `completed`, `error`, `paused`, `pending`, `requires_action`, `running`, `waiting` |
+| `status` | `completed`, `error`, `interrupting`, `paused`, `pending`, `requires_action`, `running`, `waiting` |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md
index c2cffd13d4..70cf8c4fbe 100644
--- a/docs/reference/api/schemas.md
+++ b/docs/reference/api/schemas.md
@@ -3554,9 +3554,9 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
#### Enumerated Values
-| Value(s) |
-|------------------------------------------------------------------------------------|
-| `completed`, `error`, `paused`, `pending`, `requires_action`, `running`, `waiting` |
+| Value(s) |
+|----------------------------------------------------------------------------------------------------|
+| `completed`, `error`, `interrupting`, `paused`, `pending`, `requires_action`, `running`, `waiting` |
## codersdk.ChatStreamActionRequired
diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts
index af46758f9f..41255aa499 100644
--- a/site/src/api/typesGenerated.ts
+++ b/site/src/api/typesGenerated.ts
@@ -2714,6 +2714,7 @@ export interface ChatSourcePart {
export type ChatStatus =
| "completed"
| "error"
+ | "interrupting"
| "paused"
| "pending"
| "requires_action"
@@ -2723,6 +2724,7 @@ export type ChatStatus =
export const ChatStatuses: ChatStatus[] = [
"completed",
"error",
+ "interrupting",
"paused",
"pending",
"requires_action",