package repository import ( "context" agentdto "github.com/kandev/kandev/internal/agent/dto" "github.com/kandev/kandev/internal/task/models" v1 "github.com/kandev/kandev/pkg/api/v1" ) // WorkspaceRepository handles workspace CRUD. type WorkspaceRepository interface { CreateWorkspace(ctx context.Context, workspace *models.Workspace) error GetWorkspace(ctx context.Context, id string) (*models.Workspace, error) UpdateWorkspace(ctx context.Context, workspace *models.Workspace) error DeleteWorkspace(ctx context.Context, id string) error ListWorkspaces(ctx context.Context) ([]*models.Workspace, error) } // TaskRepository handles task CRUD and workflow placement. // Note: models.TaskRepository is a struct in internal/task/models; no Go conflict exists. type TaskRepository interface { CreateTask(ctx context.Context, task *models.Task) error GetTask(ctx context.Context, id string) (*models.Task, error) UpdateTask(ctx context.Context, task *models.Task) error DeleteTask(ctx context.Context, id string) error ListTasks(ctx context.Context, workflowID string) ([]*models.Task, error) ListTasksByWorkspace(ctx context.Context, workspaceID, workflowID, repositoryID, query string, page, pageSize int, includeArchived, includeEphemeral, onlyEphemeral, excludeConfig bool) ([]*models.Task, int, error) ListTasksByWorkflowStep(ctx context.Context, workflowStepID string) ([]*models.Task, error) ArchiveTask(ctx context.Context, id string) error // ArchiveTaskIfActive is the CAS variant used by office task-handoffs // cascade archives. Returns whether the row was updated. ArchiveTaskIfActive(ctx context.Context, id, cascadeID string) (bool, error) // UnarchiveTaskByCascade clears archived_at only when the task was // archived by the named cascade. Returns whether the row was updated. UnarchiveTaskByCascade(ctx context.Context, id, cascadeID string) (bool, error) ListTasksForAutoArchive(ctx context.Context) ([]*models.Task, error) // CountOpenWatcherCreatedTasks returns the number of open watcher-created // tasks for a single (integration, watchID) pair. Open = non-archived AND // state NOT IN (COMPLETED, FAILED, CANCELLED). Used by the // orchestrator's watcher throttle gate to enforce a per-watch cap. CountOpenWatcherCreatedTasks(ctx context.Context, integration, watchID string) (int, error) UpdateTaskState(ctx context.Context, id string, state v1.TaskState) error // UpdateTaskStateIfCurrentIn atomically transitions state only when the // task's current state is in allowed. Returns the pre-update state and // whether a row was modified. UpdateTaskStateIfCurrentIn(ctx context.Context, id string, state v1.TaskState, allowed []v1.TaskState) (v1.TaskState, bool, error) CountTasksByWorkflow(ctx context.Context, workflowID string) (int, error) CountTasksByWorkflowStep(ctx context.Context, stepID string) (int, error) AddTaskToWorkflow(ctx context.Context, taskID, workflowID, workflowStepID string, position int) error RemoveTaskFromWorkflow(ctx context.Context, taskID, workflowID string) error ListTasksByProject(ctx context.Context, projectID string) ([]*models.Task, error) ListTasksByAssignee(ctx context.Context, agentInstanceID string) ([]*models.Task, error) ListTaskTree(ctx context.Context, workspaceID string, filters models.TaskTreeFilters) ([]*models.Task, error) // ListChildren returns non-archived, non-ephemeral child tasks of parentID. ListChildren(ctx context.Context, parentID string) ([]*models.Task, error) // ListChildrenIncludingArchived returns ALL child tasks of parentID, // including archived ones. Used by the office task-handoffs unarchive // cascade (phase 6) to walk a previously-archived descendant tree. ListChildrenIncludingArchived(ctx context.Context, parentID string) ([]*models.Task, error) // ReparentDirectChildren updates every row whose parent_id matches // oldParentID, replacing it with newParentID. Used by no-cascade // delete so direct children of a deleted task become roots // (newParentID="") instead of dangling pointers. Affects archived // and active rows alike. ReparentDirectChildren(ctx context.Context, oldParentID, newParentID string) error // ListSiblings returns non-archived, non-ephemeral sibling tasks for taskID. // A task is a sibling of taskID when it shares a non-empty parent_id and // the same workspace_id, and is not taskID itself. Root tasks (empty // parent_id) intentionally have NO siblings — without a non-empty common // parent, every other root in the workspace would falsely match. ListSiblings(ctx context.Context, taskID string) ([]*models.Task, error) IncrementTaskSequence(ctx context.Context, workspaceID string) (int, error) GetWorkspaceTaskPrefix(ctx context.Context, workspaceID string) (prefix, officeWorkflowID string, err error) } // TaskRepoRepository handles the task↔repository junction table (models.TaskRepository rows). // Named TaskRepoRepository to reduce reader confusion with the TaskRepository sub-interface above. type TaskRepoRepository interface { CreateTaskRepository(ctx context.Context, taskRepo *models.TaskRepository) error GetTaskRepository(ctx context.Context, id string) (*models.TaskRepository, error) ListTaskRepositories(ctx context.Context, taskID string) ([]*models.TaskRepository, error) ListTaskRepositoriesByTaskIDs(ctx context.Context, taskIDs []string) (map[string][]*models.TaskRepository, error) UpdateTaskRepository(ctx context.Context, taskRepo *models.TaskRepository) error DeleteTaskRepository(ctx context.Context, id string) error DeleteTaskRepositoriesByTask(ctx context.Context, taskID string) error GetPrimaryTaskRepository(ctx context.Context, taskID string) (*models.TaskRepository, error) } // WorkflowRepository handles workflow CRUD. type WorkflowRepository interface { CreateWorkflow(ctx context.Context, workflow *models.Workflow) error GetWorkflow(ctx context.Context, id string) (*models.Workflow, error) UpdateWorkflow(ctx context.Context, workflow *models.Workflow) error DeleteWorkflow(ctx context.Context, id string) error ListWorkflows(ctx context.Context, workspaceID string, includeHidden bool) ([]*models.Workflow, error) ReorderWorkflows(ctx context.Context, workspaceID string, workflowIDs []string) error } // MessageRepository handles message persistence and lookups. type MessageRepository interface { CreateMessage(ctx context.Context, message *models.Message) error GetMessage(ctx context.Context, id string) (*models.Message, error) GetMessageByToolCallID(ctx context.Context, sessionID, toolCallID string) (*models.Message, error) GetMessageByPendingID(ctx context.Context, sessionID, pendingID string) (*models.Message, error) FindMessageByPendingID(ctx context.Context, pendingID string) (*models.Message, error) FindMessagesByPendingID(ctx context.Context, pendingID string) ([]*models.Message, error) FindMessageByPendingIDAndQuestion(ctx context.Context, sessionID, pendingID, questionID string) (*models.Message, error) FindPendingClarificationMessagesBySessionID(ctx context.Context, sessionID string) ([]*models.Message, error) UpdateMessage(ctx context.Context, message *models.Message) error ListMessages(ctx context.Context, sessionID string) ([]*models.Message, error) ListMessagesByTurnID(ctx context.Context, turnID string) ([]*models.Message, error) ListMessagesPaginated(ctx context.Context, sessionID string, opts models.ListMessagesOptions) ([]*models.Message, bool, error) SearchMessages(ctx context.Context, sessionID string, opts models.SearchMessagesOptions) ([]*models.Message, error) DeleteMessage(ctx context.Context, id string) error } // TurnRepository handles conversation turn persistence. type TurnRepository interface { CreateTurn(ctx context.Context, turn *models.Turn) error GetTurn(ctx context.Context, id string) (*models.Turn, error) GetActiveTurnBySessionID(ctx context.Context, sessionID string) (*models.Turn, error) UpdateTurn(ctx context.Context, turn *models.Turn) error CompleteTurn(ctx context.Context, id string) error AbandonTurn(ctx context.Context, id string) error CompletePendingToolCallsForTurn(ctx context.Context, turnID string) (int64, error) ListTurnsBySession(ctx context.Context, sessionID string) ([]*models.Turn, error) } // SessionRepository handles task session lifecycle and workflow-session relationships. type SessionRepository interface { CreateTaskSession(ctx context.Context, session *models.TaskSession) error GetTaskSession(ctx context.Context, id string) (*models.TaskSession, error) GetTaskSessionByTaskID(ctx context.Context, taskID string) (*models.TaskSession, error) GetActiveTaskSessionByTaskID(ctx context.Context, taskID string) (*models.TaskSession, error) UpdateTaskSession(ctx context.Context, session *models.TaskSession) error UpdateTaskSessionState(ctx context.Context, id string, state models.TaskSessionState, errorMessage string) error ListTaskSessions(ctx context.Context, taskID string) ([]*models.TaskSession, error) ListActiveTaskSessions(ctx context.Context) ([]*models.TaskSession, error) ListActiveTaskSessionsByTaskID(ctx context.Context, taskID string) ([]*models.TaskSession, error) HasActiveTaskSessionsByAgentProfile(ctx context.Context, agentProfileID string) (bool, error) GetActiveTaskInfoByAgentProfile(ctx context.Context, agentProfileID string) ([]agentdto.ActiveTaskInfo, error) HasActiveTaskSessionsByExecutor(ctx context.Context, executorID string) (bool, error) HasActiveTaskSessionsByEnvironment(ctx context.Context, environmentID string) (bool, error) HasActiveTaskSessionsByRepository(ctx context.Context, repositoryID string) (bool, error) CountActiveTaskSessionsByRepository(ctx context.Context, repositoryID string) (int, error) DeleteEphemeralTasksByAgentProfile(ctx context.Context, agentProfileID string) (int64, error) DeleteTaskSession(ctx context.Context, id string) error GetPrimarySessionByTaskID(ctx context.Context, taskID string) (*models.TaskSession, error) GetPrimarySessionIDsByTaskIDs(ctx context.Context, taskIDs []string) (map[string]string, error) GetSessionCountsByTaskIDs(ctx context.Context, taskIDs []string) (map[string]int, error) GetPrimarySessionInfoByTaskIDs(ctx context.Context, taskIDs []string) (map[string]*models.TaskSession, error) // BatchGetSessionsByTaskIDs returns every session for the given task IDs // grouped by task ID, ordered by started_at DESC within each task. One // query (chunked to stay within SQLite's host-parameter limit) replaces // per-task GetSession loops on the task-list path. BatchGetSessionsByTaskIDs(ctx context.Context, taskIDs []string) (map[string][]*models.TaskSession, error) SetSessionPrimary(ctx context.Context, sessionID string) error UpdateSessionReviewStatus(ctx context.Context, sessionID string, status string) error UpdateSessionMetadata(ctx context.Context, sessionID string, metadata map[string]interface{}) error SetSessionMetadataKey(ctx context.Context, sessionID, key string, value interface{}) error GetLastAgentMessage(ctx context.Context, sessionID string) (string, error) } // SessionWorktreeRepository handles the task session↔worktree association. type SessionWorktreeRepository interface { CreateTaskSessionWorktree(ctx context.Context, sessionWorktree *models.TaskSessionWorktree) error UpdateTaskSessionWorktreeBranch(ctx context.Context, sessionID, branch string) error ListTaskSessionWorktrees(ctx context.Context, sessionID string) ([]*models.TaskSessionWorktree, error) ListWorktreesBySessionIDs(ctx context.Context, sessionIDs []string) (map[string][]*models.TaskSessionWorktree, error) DeleteTaskSessionWorktree(ctx context.Context, id string) error DeleteTaskSessionWorktreesBySession(ctx context.Context, sessionID string) error } // GitSnapshotRepository handles git snapshots and session commit records. type GitSnapshotRepository interface { CreateGitSnapshot(ctx context.Context, snapshot *models.GitSnapshot) error GetLatestGitSnapshot(ctx context.Context, sessionID string) (*models.GitSnapshot, error) GetFirstGitSnapshot(ctx context.Context, sessionID string) (*models.GitSnapshot, error) GetGitSnapshotsBySession(ctx context.Context, sessionID string, limit int) ([]*models.GitSnapshot, error) CreateSessionCommit(ctx context.Context, commit *models.SessionCommit) error GetSessionCommits(ctx context.Context, sessionID string) ([]*models.SessionCommit, error) GetLatestSessionCommit(ctx context.Context, sessionID string) (*models.SessionCommit, error) DeleteSessionCommit(ctx context.Context, id string) error } // RepositoryEntityRepository handles git repository entity CRUD and repository scripts. // Named RepositoryEntityRepository to avoid conflation with the Repository interface itself; // mirrors the sqlite/repository_entity.go implementation file. type RepositoryEntityRepository interface { CreateRepository(ctx context.Context, repository *models.Repository) error GetRepository(ctx context.Context, id string) (*models.Repository, error) UpdateRepository(ctx context.Context, repository *models.Repository) error DeleteRepository(ctx context.Context, id string) error ListRepositories(ctx context.Context, workspaceID string) ([]*models.Repository, error) CreateRepositoryScript(ctx context.Context, script *models.RepositoryScript) error GetRepositoryScript(ctx context.Context, id string) (*models.RepositoryScript, error) UpdateRepositoryScript(ctx context.Context, script *models.RepositoryScript) error DeleteRepositoryScript(ctx context.Context, id string) error ListRepositoryScripts(ctx context.Context, repositoryID string) ([]*models.RepositoryScript, error) ListScriptsByRepositoryIDs(ctx context.Context, repoIDs []string) (map[string][]*models.RepositoryScript, error) GetRepositoryByProviderInfo(ctx context.Context, workspaceID, provider, owner, name string) (*models.Repository, error) } // ExecutorRepository handles executor CRUD, executor profiles, and running state. type ExecutorRepository interface { CreateExecutor(ctx context.Context, executor *models.Executor) error GetExecutor(ctx context.Context, id string) (*models.Executor, error) UpdateExecutor(ctx context.Context, executor *models.Executor) error DeleteExecutor(ctx context.Context, id string) error ListExecutors(ctx context.Context) ([]*models.Executor, error) CreateExecutorProfile(ctx context.Context, profile *models.ExecutorProfile) error GetExecutorProfile(ctx context.Context, id string) (*models.ExecutorProfile, error) UpdateExecutorProfile(ctx context.Context, profile *models.ExecutorProfile) error DeleteExecutorProfile(ctx context.Context, id string) error ListExecutorProfiles(ctx context.Context, executorID string) ([]*models.ExecutorProfile, error) ListAllExecutorProfiles(ctx context.Context) ([]*models.ExecutorProfile, error) ListExecutorsRunning(ctx context.Context) ([]*models.ExecutorRunning, error) UpsertExecutorRunning(ctx context.Context, running *models.ExecutorRunning) error GetExecutorRunningBySessionID(ctx context.Context, sessionID string) (*models.ExecutorRunning, error) DeleteExecutorRunningBySessionID(ctx context.Context, sessionID string) error // HasExecutorRunningRow returns true if a row exists for the session. // Used to decide "session has been launched at least once" without loading the full row. HasExecutorRunningRow(ctx context.Context, sessionID string) (bool, error) // UpdateResumeToken performs a CAS-style narrow update of resume_token + last_message_uuid // scoped to the row's current agent_execution_id. If the row's agent_execution_id no longer // matches expectedExecID (i.e. a new execution has taken over), returns models.ErrExecutionRotated // and writes nothing. Use when persisting state from a specific execution that may have been // replaced concurrently — typically resume tokens emitted by ACP session events. UpdateResumeToken(ctx context.Context, sessionID, expectedExecID, resumeToken, lastMessageUUID string) error // UpdateExecutorRunningStatus performs a narrow status update on the row. // Used when the agent process is intentionally not being started (prepare-only // launch) so the row doesn't sit on the misleading default "starting" forever. // Returns models.ErrExecutorRunningNotFound if no row exists for the session. UpdateExecutorRunningStatus(ctx context.Context, sessionID, status string) error } // EnvironmentRepository handles environment CRUD. type EnvironmentRepository interface { CreateEnvironment(ctx context.Context, environment *models.Environment) error GetEnvironment(ctx context.Context, id string) (*models.Environment, error) UpdateEnvironment(ctx context.Context, environment *models.Environment) error DeleteEnvironment(ctx context.Context, id string) error ListEnvironments(ctx context.Context) ([]*models.Environment, error) } // TaskEnvironmentRepository handles per-task execution environment instances // and their per-repository child rows. type TaskEnvironmentRepository interface { CreateTaskEnvironment(ctx context.Context, env *models.TaskEnvironment) error GetTaskEnvironment(ctx context.Context, id string) (*models.TaskEnvironment, error) GetTaskEnvironmentByTaskID(ctx context.Context, taskID string) (*models.TaskEnvironment, error) UpdateTaskEnvironment(ctx context.Context, env *models.TaskEnvironment) error DeleteTaskEnvironment(ctx context.Context, id string) error DeleteTaskEnvironmentsByTask(ctx context.Context, taskID string) error CreateTaskEnvironmentRepo(ctx context.Context, repo *models.TaskEnvironmentRepo) error ListTaskEnvironmentRepos(ctx context.Context, envID string) ([]*models.TaskEnvironmentRepo, error) UpdateTaskEnvironmentRepo(ctx context.Context, repo *models.TaskEnvironmentRepo) error DeleteTaskEnvironmentRepo(ctx context.Context, id string) error DeleteTaskEnvironmentReposByEnv(ctx context.Context, envID string) error } // ReviewRepository handles session file review records. type ReviewRepository interface { UpsertSessionFileReview(ctx context.Context, review *models.SessionFileReview) error GetSessionFileReviews(ctx context.Context, sessionID string) ([]*models.SessionFileReview, error) DeleteSessionFileReviews(ctx context.Context, sessionID string) error } // DocumentRepository handles task document CRUD and revision history. // Documents generalize plans: each document is identified by a unique key within a task. type DocumentRepository interface { CreateDocument(ctx context.Context, doc *models.TaskDocument) error GetDocument(ctx context.Context, taskID, key string) (*models.TaskDocument, error) UpdateDocument(ctx context.Context, doc *models.TaskDocument) error DeleteDocument(ctx context.Context, taskID, key string) error ListDocuments(ctx context.Context, taskID string) ([]*models.TaskDocument, error) // Revision history InsertDocumentRevision(ctx context.Context, rev *models.TaskDocumentRevision) error GetLatestDocumentRevision(ctx context.Context, taskID, key string) (*models.TaskDocumentRevision, error) ListDocumentRevisions(ctx context.Context, taskID, key string, limit int) ([]*models.TaskDocumentRevision, error) GetDocumentRevision(ctx context.Context, id string) (*models.TaskDocumentRevision, error) NextDocumentRevisionNumber(ctx context.Context, taskID, key string) (int, error) // WriteDocumentRevision atomically upserts the HEAD document and writes/merges a revision // in a single transaction. Pass a non-nil coalesceLatestID to merge into an existing revision; // otherwise a new revision is appended with revision_number computed inside the tx. WriteDocumentRevision(ctx context.Context, head *models.TaskDocument, rev *models.TaskDocumentRevision, coalesceLatestID *string) error } // PlanRepository handles task plan CRUD and its revision history. type PlanRepository interface { CreateTaskPlan(ctx context.Context, plan *models.TaskPlan) error GetTaskPlan(ctx context.Context, taskID string) (*models.TaskPlan, error) UpdateTaskPlan(ctx context.Context, plan *models.TaskPlan) error DeleteTaskPlan(ctx context.Context, taskID string) error // Revision history InsertTaskPlanRevision(ctx context.Context, rev *models.TaskPlanRevision) error UpdateTaskPlanRevision(ctx context.Context, rev *models.TaskPlanRevision) error GetTaskPlanRevision(ctx context.Context, id string) (*models.TaskPlanRevision, error) GetLatestTaskPlanRevision(ctx context.Context, taskID string) (*models.TaskPlanRevision, error) ListTaskPlanRevisions(ctx context.Context, taskID string, limit int) ([]*models.TaskPlanRevision, error) NextTaskPlanRevisionNumber(ctx context.Context, taskID string) (int, error) // WritePlanRevision atomically upserts the HEAD plan and writes/merges a revision in a // single transaction. Pass a non-nil coalesceLatestID to merge into an existing revision; // otherwise a new revision is appended with revision_number computed inside the tx. WritePlanRevision(ctx context.Context, head *models.TaskPlan, rev *models.TaskPlanRevision, coalesceLatestID *string) error }