From 96fbc79cd2f69b330d4b9d80074baf134fd0f5e0 Mon Sep 17 00:00:00 2001 From: viettranx Date: Tue, 24 Mar 2026 23:28:33 +0700 Subject: [PATCH] refactor(store): split TeamStore into focused sub-interfaces Split monolithic TeamStore (61 methods) into 6 focused interfaces: - TeamCRUDStore: core team + member operations (13 methods) - TaskStore: task CRUD + lifecycle transitions (22 methods) - TaskCommentStore: comments, events, attachments (10 methods) - TaskRecoveryStore: stale detection + recovery (6 methods) - TaskFollowupStore: follow-up scheduling (7 methods) - TeamAccessStore: user access grants (5 methods) TeamStore is now a composed interface embedding all sub-interfaces, maintaining full backward compatibility. No implementation changes. --- internal/store/team_store.go | 128 +++++++++++------------------------ 1 file changed, 38 insertions(+), 90 deletions(-) diff --git a/internal/store/team_store.go b/internal/store/team_store.go index 5c50ff81..f0189766 100644 --- a/internal/store/team_store.go +++ b/internal/store/team_store.go @@ -192,150 +192,98 @@ type ScopeEntry struct { ChatID string `json:"chat_id"` } -// TeamStore manages agent teams, tasks, and messages. -type TeamStore interface { - // Team CRUD +// TeamCRUDStore manages core team and member operations. +type TeamCRUDStore interface { CreateTeam(ctx context.Context, team *TeamData) error GetTeam(ctx context.Context, teamID uuid.UUID) (*TeamData, error) GetTeamUnscoped(ctx context.Context, id uuid.UUID) (*TeamData, error) UpdateTeam(ctx context.Context, teamID uuid.UUID, updates map[string]any) error DeleteTeam(ctx context.Context, teamID uuid.UUID) error ListTeams(ctx context.Context) ([]TeamData, error) - - // Members AddMember(ctx context.Context, teamID, agentID uuid.UUID, role string) error RemoveMember(ctx context.Context, teamID, agentID uuid.UUID) error ListMembers(ctx context.Context, teamID uuid.UUID) ([]TeamMemberData, error) - // ListIdleMembers returns team members (non-lead) that have no in_progress tasks. ListIdleMembers(ctx context.Context, teamID uuid.UUID) ([]TeamMemberData, error) - - // GetTeamForAgent returns the team that the given agent belongs to. - // Returns nil, nil if the agent is not in any team. GetTeamForAgent(ctx context.Context, agentID uuid.UUID) (*TeamData, error) - - // KnownUserIDs returns distinct user IDs from sessions of team member agents. - // Used by team settings UI to populate user select boxes. KnownUserIDs(ctx context.Context, teamID uuid.UUID, limit int) ([]string, error) - - // Scopes (channel+chatID discovery across tasks and workspace) ListTaskScopes(ctx context.Context, teamID uuid.UUID) ([]ScopeEntry, error) +} - // Tasks (shared task list) +// TaskStore manages task CRUD, lifecycle transitions, and progress. +type TaskStore interface { CreateTask(ctx context.Context, task *TeamTaskData) error UpdateTask(ctx context.Context, taskID uuid.UUID, updates map[string]any) error - // ListTasks returns tasks for a team. orderBy: "priority" or "newest". - // statusFilter: "" = non-completed (default), "completed", "all". - // userID: if non-empty, filter to tasks created by this user. - // channel+chatID: if either is non-empty, filter to that exact scope. - // limit: max rows to return; 0 uses the default (30). ListTasks(ctx context.Context, teamID uuid.UUID, orderBy string, statusFilter string, userID string, channel string, chatID string, limit int, offset int) ([]TeamTaskData, error) - // GetTask returns a single task by ID with joined agent info. GetTask(ctx context.Context, taskID uuid.UUID) (*TeamTaskData, error) - // GetTasksByIDs returns multiple tasks by IDs in a single query. GetTasksByIDs(ctx context.Context, ids []uuid.UUID) ([]TeamTaskData, error) - // SearchTasks performs FTS search over task subject+description. - // userID: if non-empty, filter to tasks created by this user. SearchTasks(ctx context.Context, teamID uuid.UUID, query string, limit int, userID string) ([]TeamTaskData, error) - // DeleteTask permanently removes a terminal-status task (completed/failed/cancelled). - // Returns ErrTaskNotFound if the task does not exist or is not in a terminal status. DeleteTask(ctx context.Context, taskID, teamID uuid.UUID) error - // DeleteTasks permanently removes multiple terminal-status tasks in a single query. - // Returns the list of IDs that were actually deleted. DeleteTasks(ctx context.Context, taskIDs []uuid.UUID, teamID uuid.UUID) ([]uuid.UUID, error) - - // ClaimTask atomically transitions a task from pending to in_progress. - // Only one agent can claim a given task (row-level lock, race-safe). - // teamID is validated in the WHERE clause to prevent cross-team task claiming. ClaimTask(ctx context.Context, taskID, agentID, teamID uuid.UUID) error - - // AssignTask admin-assigns a pending task to a specific agent. - // teamID is validated in the WHERE clause to prevent cross-team assignment. AssignTask(ctx context.Context, taskID, agentID, teamID uuid.UUID) error - - // CompleteTask marks a task as completed and unblocks dependent tasks. - // teamID is validated in the WHERE clause to prevent cross-team task completion. CompleteTask(ctx context.Context, taskID, teamID uuid.UUID, result string) error - - // CancelTask marks a non-completed task as cancelled (status=completed, result="CANCELLED: ..."), - // unblocks dependent tasks, and transitions blocked→pending when all blockers are resolved. - // Returns error if the task is already completed or not found. CancelTask(ctx context.Context, taskID, teamID uuid.UUID, reason string) error - - // FailTask marks an in_progress task as failed and stores the error message. - // Unblocks dependent tasks so they aren't stuck. FailTask(ctx context.Context, taskID, teamID uuid.UUID, errMsg string) error - // FailPendingTask marks a pending or blocked task as failed (post-turn validation). - // Unlike FailTask, accepts pending/blocked source statuses. FailPendingTask(ctx context.Context, taskID, teamID uuid.UUID, errMsg string) error - - // Review workflow ReviewTask(ctx context.Context, taskID, teamID uuid.UUID) error ApproveTask(ctx context.Context, taskID, teamID uuid.UUID, comment string) error RejectTask(ctx context.Context, taskID, teamID uuid.UUID, reason string) error + UpdateTaskProgress(ctx context.Context, taskID, teamID uuid.UUID, percent int, step string) error + RenewTaskLock(ctx context.Context, taskID, teamID uuid.UUID) error + ResetTaskStatus(ctx context.Context, taskID, teamID uuid.UUID) error +} - // Task comments +// TaskCommentStore manages task comments, audit events, and attachments. +type TaskCommentStore interface { AddTaskComment(ctx context.Context, comment *TeamTaskCommentData) error ListTaskComments(ctx context.Context, taskID uuid.UUID) ([]TeamTaskCommentData, error) ListRecentTaskComments(ctx context.Context, taskID uuid.UUID, limit int) ([]TeamTaskCommentData, error) - - // Audit events RecordTaskEvent(ctx context.Context, event *TeamTaskEventData) error ListTaskEvents(ctx context.Context, taskID uuid.UUID) ([]TeamTaskEventData, error) - // ListTeamEvents returns recent events across all tasks in a team. ListTeamEvents(ctx context.Context, teamID uuid.UUID, limit, offset int) ([]TeamTaskEventData, error) - - // Attachments (path-based, no FK to workspace files) AttachFileToTask(ctx context.Context, att *TeamTaskAttachmentData) error GetAttachment(ctx context.Context, attachmentID uuid.UUID) (*TeamTaskAttachmentData, error) ListTaskAttachments(ctx context.Context, taskID uuid.UUID) ([]TeamTaskAttachmentData, error) DetachFileFromTask(ctx context.Context, taskID uuid.UUID, path string) error +} - // Follow-up reminders +// TaskRecoveryStore manages stale task detection and recovery. +type TaskRecoveryStore interface { + RecoverAllStaleTasks(ctx context.Context) ([]RecoveredTaskInfo, error) + ForceRecoverAllTasks(ctx context.Context) ([]RecoveredTaskInfo, error) + ListRecoverableTasks(ctx context.Context, teamID uuid.UUID) ([]TeamTaskData, error) + MarkAllStaleTasks(ctx context.Context, olderThan time.Time) ([]RecoveredTaskInfo, error) + MarkInReviewStaleTasks(ctx context.Context, olderThan time.Time) ([]RecoveredTaskInfo, error) + FixOrphanedBlockedTasks(ctx context.Context) ([]RecoveredTaskInfo, error) +} + +// TaskFollowupStore manages follow-up reminder scheduling. +type TaskFollowupStore interface { SetTaskFollowup(ctx context.Context, taskID, teamID uuid.UUID, followupAt time.Time, max int, message, channel, chatID string) error ClearTaskFollowup(ctx context.Context, taskID uuid.UUID) error - // ListAllFollowupDueTasks returns due followup tasks across all v2 active teams (batch). ListAllFollowupDueTasks(ctx context.Context) ([]TeamTaskData, error) IncrementFollowupCount(ctx context.Context, taskID uuid.UUID, nextAt *time.Time) error - - // Auto follow-up guardrails (system-level, no LLM dependency) - // ClearFollowupByScope clears followup on all in_progress tasks for a given channel+chatID scope. ClearFollowupByScope(ctx context.Context, channel, chatID string) (int, error) - // SetFollowupForActiveTasks sets followup on in_progress tasks that don't already have one. - // Matches tasks scoped to the given channel+chatID, or unscoped tasks in the same team. SetFollowupForActiveTasks(ctx context.Context, teamID uuid.UUID, channel, chatID string, followupAt time.Time, max int, message string) (int, error) - // HasActiveMemberTasks returns true if there are pending/in_progress/blocked tasks - // assigned to agents other than the given agent (typically the lead). - // Used to suppress auto-followup when the lead is waiting for teammates, not the user. HasActiveMemberTasks(ctx context.Context, teamID uuid.UUID, excludeAgentID uuid.UUID) (bool, error) +} - // Progress - UpdateTaskProgress(ctx context.Context, taskID, teamID uuid.UUID, percent int, step string) error - - // Lock renewal (heartbeat to prevent stale recovery of long-running tasks) - RenewTaskLock(ctx context.Context, taskID, teamID uuid.UUID) error - - // Stale recovery (batch — all v2 active teams in single query) - // RecoverAllStaleTasks resets in_progress tasks with expired locks back to pending. - RecoverAllStaleTasks(ctx context.Context) ([]RecoveredTaskInfo, error) - // ForceRecoverAllTasks resets ALL in_progress tasks back to pending (ignoring lock expiry). - // Used on startup when no agents are running. - ForceRecoverAllTasks(ctx context.Context) ([]RecoveredTaskInfo, error) - // ListRecoverableTasks returns all pending tasks (including stale in_progress with expired locks). - // Per-team: used by DispatchUnblockedTasks after task completion. - ListRecoverableTasks(ctx context.Context, teamID uuid.UUID) ([]TeamTaskData, error) - // MarkAllStaleTasks sets pending tasks older than olderThan to stale status across all v2 active teams. - MarkAllStaleTasks(ctx context.Context, olderThan time.Time) ([]RecoveredTaskInfo, error) - // MarkInReviewStaleTasks sets in_review tasks older than olderThan to stale across all v2 active teams. - MarkInReviewStaleTasks(ctx context.Context, olderThan time.Time) ([]RecoveredTaskInfo, error) - // FixOrphanedBlockedTasks unblocks blocked tasks where all blockers reached terminal status. - FixOrphanedBlockedTasks(ctx context.Context) ([]RecoveredTaskInfo, error) - // ResetTaskStatus resets a stale or failed task back to pending for retry. - ResetTaskStatus(ctx context.Context, taskID, teamID uuid.UUID) error - - // Team user grants +// TeamAccessStore manages user-level team access grants. +type TeamAccessStore interface { GrantTeamAccess(ctx context.Context, teamID uuid.UUID, userID, role, grantedBy string) error RevokeTeamAccess(ctx context.Context, teamID uuid.UUID, userID string) error ListTeamGrants(ctx context.Context, teamID uuid.UUID) ([]TeamUserGrant, error) ListUserTeams(ctx context.Context, userID string) ([]TeamData, error) HasTeamAccess(ctx context.Context, teamID uuid.UUID, userID string) (bool, error) } + +// TeamStore composes all team sub-interfaces for backward compatibility. +// New code should depend on the specific sub-interface it needs. +type TeamStore interface { + TeamCRUDStore + TaskStore + TaskCommentStore + TaskRecoveryStore + TaskFollowupStore + TeamAccessStore +}