import Database from 'better-sqlite3'; import { logger } from '../logger.js'; import * as schemaRepo from './repositories/schema.js'; import * as jobsRepo from './repositories/jobs.js'; import { JobStatus, Job, CreateJobParams, SubtaskInfo } from './repositories/jobs.js'; import * as spacesRepo from './repositories/spaces.js'; import { Space, SpaceMemberRoleValue, SpaceMember, SpaceInviteRole, SpaceInvite, CreateSpaceParams } from './repositories/spaces.js'; import * as usersRepo from './repositories/users.js'; import { User, CreateUserParams, FindOrCreateByOAuthParams, CreateLocalUserParams, LocalOrg, LocalOrgMember, GiteaOrgInput, GiteaOrg } from './repositories/users.js'; import * as workerNodesRepo from './repositories/worker-nodes.js'; import { WorkerNode, UpsertWorkerNodeParams } from './repositories/worker-nodes.js'; import * as scheduledTasksRepo from './repositories/scheduled-tasks.js'; import { ScheduledTask, CreateScheduledTaskParams, UpdateScheduledTaskParams } from './repositories/scheduled-tasks.js'; import * as a2aRepo from './repositories/a2a.js'; import { A2aClientRow, A2aDelegationRow } from './repositories/a2a.js'; import * as reflectionRepo from './repositories/reflection.js'; import * as toolRequestsRepo from './repositories/tool-requests.js'; import { ToolRequestCategory, ToolRequestStatus, ToolRequest, ToolRequestAggregate, PackageRequest, PackageRequestStatus } from './repositories/tool-requests.js'; import * as gatewayRepo from './repositories/gateway.js'; import { GatewayVirtualKeySource, GatewayVirtualKey, GatewayKeyUsage } from './repositories/gateway.js'; import * as llmUsageRepo from './repositories/llm-usage.js'; import { LlmUsageIncrement, LlmUsageDailyAgg, LlmUsageHourlyIncrement, LlmUsageHourlyRow } from './repositories/llm-usage.js'; import * as notificationsRepo from './repositories/notifications.js'; import { PushSubscriptionRecord, UpsertPushSubscriptionInput, NotificationPrefs, NotificationPrefsUpdate } from './repositories/notifications.js'; import * as auditRepo from './repositories/audit.js'; import * as appSharesRepo from './repositories/app-shares.js'; import * as provenanceRepo from './repositories/provenance.js'; import * as localTasksRepo from './repositories/local-tasks.js'; import { LocalTask, MissionBrief, LocalTaskComment, CreateLocalTaskParams } from './repositories/local-tasks.js'; export { MISSION_BRIEF_FIELDS } from './repositories/local-tasks.js'; export type { TitleSource, LocalTask, MissionBriefField, MissionBrief, LocalTaskComment, CreateLocalTaskParams } from './repositories/local-tasks.js'; import * as taskSearchRepo from './repositories/task-search.js'; import * as calendarRepo from './repositories/calendar.js'; import { CalendarEvent, CalendarDayCounts, CalendarDayTask, CrossSpaceCalendarMonth } from './repositories/calendar.js'; export { enumerateLocalDays } from './repositories/calendar.js'; export type { CalendarEvent, CalendarDayCounts, CalendarDayTask, CrossCalendarSpace, CrossSpaceCalendarMonth } from './repositories/calendar.js'; export type { NotifyEventType, PushSubscriptionRecord, UpsertPushSubscriptionInput, NotificationPrefs, NotificationPrefsUpdate } from './repositories/notifications.js'; export type { LlmUsageIncrement, LlmUsageDailyAgg, LlmUsageHourlyIncrement, LlmUsageHourlyRow } from './repositories/llm-usage.js'; export type { GatewayVirtualKeySource, GatewayVirtualKey, GatewayKeyUsage } from './repositories/gateway.js'; export type { ToolRequestCategory, ToolRequestStatus, ToolRequest, ToolRequestAggregate, PackageRequest, PackageRequestStatus } from './repositories/tool-requests.js'; export type { A2aClientRow, A2aDelegationRow } from './repositories/a2a.js'; export type { ScheduledTaskKind, ScheduledTask, CreateScheduledTaskParams, UpdateScheduledTaskParams } from './repositories/scheduled-tasks.js'; export type { WorkerNode, UpsertWorkerNodeParams } from './repositories/worker-nodes.js'; export type { User, CreateUserParams, FindOrCreateByOAuthParams, CreateLocalUserParams, LocalOrg, LocalOrgMember, GiteaOrgInput, GiteaOrg } from './repositories/users.js'; export type { Space, SpaceMemberRoleValue, SpaceMember, SpaceInviteRole, SpaceInvite, CreateSpaceParams } from './repositories/spaces.js'; export type { JobStatus, JobRole, JobProfile, TaskClass, Job, CreateJobParams, SubtaskInfo } from './repositories/jobs.js'; export { localTaskRepoName } from './repositories/shared.js'; export class Repository { private readonly db: Database.Database; constructor(dbPath: string) { this.db = new Database(dbPath); this.db.pragma('journal_mode = WAL'); this.db.pragma('foreign_keys = ON'); this.db.pragma('busy_timeout = 5000'); this.initSchema(); logger.info(`Repository: initialized at ${dbPath}`); } private initSchema(): void { return schemaRepo.initSchema(this.db); } private runPreSchemaCompatibilityMigrations(): void { return schemaRepo.runPreSchemaCompatibilityMigrations(this.db); } private ensureColumn(tableName: string, columnName: string, definition: string): void { return schemaRepo.ensureColumn(this.db, tableName, columnName, definition); } private migrateWaitingSubtasksStatus(): void { return schemaRepo.migrateWaitingSubtasksStatus(this.db); } private insertJobSync(params: CreateJobParams): Job { return jobsRepo.insertJobSync(this.db, params); } private getJobSync(id: string): Job | null { return jobsRepo.getJobSync(this.db, id); } private async buildSubtaskInfos(subJobs: Job[], maxDepth: number = 3): Promise { return jobsRepo.buildSubtaskInfos(this.db, subJobs, maxDepth); } async createJob(params: CreateJobParams): Promise { return jobsRepo.createJob(this.db, params); } createJobIfNoPending(params: CreateJobParams, opts?: { blockOnToolRequestPause?: boolean }): { job: Job; created: boolean; blockedByToolRequest?: boolean } { return jobsRepo.createJobIfNoPending(this.db, params, opts); } async getJob(id: string, opts?: { viewer?: Express.User }): Promise { return jobsRepo.getJob(this.db, id, opts); } async createSpace(params: CreateSpaceParams): Promise { return spacesRepo.createSpace(this.db, params); } async getSpace(id: string, opts?: { viewer?: Express.User }): Promise { return spacesRepo.getSpace(this.db, id, opts); } async listSpaces(opts?: { viewer?: Express.User; includeArchived?: boolean }): Promise { return spacesRepo.listSpaces(this.db, opts); } async updateSpace(id: string, patch: { title?: string; description?: string; brandColor?: string | null; visibility?: 'private' | 'org' | 'public'; visibilityScopeOrgId?: string | null }): Promise { return spacesRepo.updateSpace(this.db, id, patch); } async archiveSpace(id: string): Promise { return spacesRepo.archiveSpace(this.db, id); } async setSpaceWorkspaceDir(id: string, workspaceDir: string): Promise { return spacesRepo.setSpaceWorkspaceDir(this.db, id, workspaceDir); } async hardDeleteSpace(id: string): Promise { return spacesRepo.hardDeleteSpace(this.db, id); } async ensurePersonalSpace(ownerId: string, displayName?: string): Promise { return spacesRepo.ensurePersonalSpace(this.db, ownerId, displayName); } async addSpaceMember(params: { spaceId: string; userId: string; role: SpaceMemberRoleValue; invitedBy?: string | null; }): Promise { return spacesRepo.addSpaceMember(this.db, params); } async listSpaceMembers(spaceId: string): Promise { return spacesRepo.listSpaceMembers(this.db, spaceId); } getSpaceMemberRole(spaceId: string, userId: string): SpaceMemberRoleValue | null { return spacesRepo.getSpaceMemberRole(this.db, spaceId, userId); } getSpaceToolPolicy(spaceId: string): string | null { return spacesRepo.getSpaceToolPolicy(this.db, spaceId); } setSpaceToolPolicy(spaceId: string, policyJson: string | null): void { return spacesRepo.setSpaceToolPolicy(this.db, spaceId, policyJson); } getSpacePythonPackages(spaceId: string): string | null { return spacesRepo.getSpacePythonPackages(this.db, spaceId); } setSpacePythonPackages(spaceId: string, packagesJson: string | null): void { return spacesRepo.setSpacePythonPackages(this.db, spaceId, packagesJson); } userCanViewSpace(spaceId: string, user: { id: string; role: string }): boolean { return spacesRepo.userCanViewSpace(this.db, spaceId, user); } async updateSpaceMemberRole(spaceId: string, userId: string, role: SpaceMemberRoleValue): Promise { return spacesRepo.updateSpaceMemberRole(this.db, spaceId, userId, role); } async removeSpaceMember(spaceId: string, userId: string): Promise { return spacesRepo.removeSpaceMember(this.db, spaceId, userId); } private rowToSpaceInvite(row: { token: string; space_id: string; role: string; created_by: string | null; created_at: string; expires_at: string | null; revoked_at: string | null; }): SpaceInvite { return spacesRepo.rowToSpaceInvite(row); } isSpaceInviteValid(invite: SpaceInvite | null | undefined): invite is SpaceInvite { return spacesRepo.isSpaceInviteValid(this.db, invite); } createSpaceInvite(params: { spaceId: string; role: SpaceInviteRole; createdBy?: string | null; expiresInDays?: number | null; }): SpaceInvite { return spacesRepo.createSpaceInvite(this.db, params); } getSpaceInvite(spaceId: string): SpaceInvite | null { return spacesRepo.getSpaceInvite(this.db, spaceId); } getSpaceInviteByToken(token: string): SpaceInvite | null { return spacesRepo.getSpaceInviteByToken(this.db, token); } revokeSpaceInvite(spaceId: string): void { return spacesRepo.revokeSpaceInvite(this.db, spaceId); } createAppShareLink(spaceId: string, appName: string, createdBy: string | null): { token: string } { return appSharesRepo.createAppShareLink(this.db, spaceId, appName, createdBy); } getAppShareLink(spaceId: string, appName: string): { token: string; revokedAt: string | null } | null { return appSharesRepo.getAppShareLink(this.db, spaceId, appName); } revokeAppShareLink(spaceId: string, appName: string): void { return appSharesRepo.revokeAppShareLink(this.db, spaceId, appName); } resolveAppShareToken(token: string): { spaceId: string; appName: string } | null { return appSharesRepo.resolveAppShareToken(this.db, token); } async resolveTaskFolderContext(taskId: number, userFolderRoot: string): Promise { return localTasksRepo.resolveTaskFolderContext(this.db, taskId, userFolderRoot); } async createLocalTask(params: CreateLocalTaskParams): Promise { return localTasksRepo.createLocalTask(this.db, params); } async getLocalTask(taskId: number, opts?: { viewer?: Express.User }): Promise { return localTasksRepo.getLocalTask(this.db, taskId, opts); } async shareLocalTask(taskId: number): Promise { return localTasksRepo.shareLocalTask(this.db, taskId); } async unshareLocalTask(taskId: number): Promise { return localTasksRepo.unshareLocalTask(this.db, taskId); } async updateMissionBrief(taskId: number, patch: Partial): Promise { return localTasksRepo.updateMissionBrief(this.db, taskId, patch); } updateMissionBriefSync(taskId: number, patch: Partial): MissionBrief | null { return localTasksRepo.updateMissionBriefSync(this.db, taskId, patch); } getMissionBriefSync(taskId: number): MissionBrief | null { return localTasksRepo.getMissionBriefSync(this.db, taskId); } makeMissionBriefIO(taskId: number): import('../engine/tools/core.js').MissionBriefIO { return localTasksRepo.makeMissionBriefIO(this.db, taskId); } makeTaskConversationIO(taskId: number, transcriptPath?: string): import('../engine/tools/core.js').TaskConversationIO { return localTasksRepo.makeTaskConversationIO(this.db, taskId, transcriptPath); } makeTranscriptOnlyConversationIO(transcriptPath: string | undefined): import('../engine/tools/core.js').TaskConversationIO { return localTasksRepo.makeTranscriptOnlyConversationIO(transcriptPath); } makeWorkspaceTaskSearchIO(currentTaskId: number, ownerId: string): import('../engine/tools/core.js').WorkspaceTaskSearchIO { return taskSearchRepo.makeWorkspaceTaskSearchIO(this.db, currentTaskId, ownerId); } recordProvenance(params: { workspacePath: string; relPath: string; event: 'create' | 'modify'; sourceKind?: import('../engine/tools/core.js').FileProvenanceSourceKind; spaceId?: string | null; taskId?: number | null; jobId?: string | null; piece?: string | null; movement?: string | null; checksum?: string | null; note?: string | null; }): void { return provenanceRepo.recordProvenance(this.db, params); } getProvenance(workspacePath: string, relPath: string): import('../engine/tools/core.js').FileProvenanceRecord | null { return provenanceRepo.getProvenance(this.db, workspacePath, relPath); } listProvenance(workspacePath: string, filters?: import('../engine/tools/core.js').FileProvenanceListFilters): import('../engine/tools/core.js').FileProvenanceRecord[] { return provenanceRepo.listProvenance(this.db, workspacePath, filters); } makeFileProvenanceIO(workspacePath: string): import('../engine/tools/core.js').FileProvenanceIO { return provenanceRepo.makeFileProvenanceIO(this.db, workspacePath); } async getLocalTaskByShareToken(token: string): Promise { return localTasksRepo.getLocalTaskByShareToken(this.db, token); } async listLocalTasks(filter?: { ownerId?: string; viewer?: Express.User }): Promise { return localTasksRepo.listLocalTasks(this.db, filter); } async updateLocalTask(taskId: number, updates: Partial>): Promise { return localTasksRepo.updateLocalTask(this.db, taskId, updates); } async updateFeedback(taskId: number, feedback: { rating: 'good' | 'bad'; tags: string[]; comment: string | null; }): Promise { return localTasksRepo.updateFeedback(this.db, taskId, feedback); } async addLocalTaskComment(taskId: number, author: string, body: string, kind: string = 'comment', attachments: string[] = []): Promise { return localTasksRepo.addLocalTaskComment(this.db, taskId, author, body, kind, attachments); } indexTaskComment(commentId: number, taskId: number, author: string, kind: string, createdAt: string, body: string, attachments: string | null): void { return taskSearchRepo.indexTaskComment(this.db, commentId, taskId, author, kind, createdAt, body, attachments); } private buildFtsMatch(terms: string[]): string { return taskSearchRepo.buildFtsMatch(terms); } async searchWorkspaceTasks(currentTaskId: number, ownerId: string, query: string, opts: { limit?: number; kind?: string; author?: string; taskId?: number } = {}): Promise> { return taskSearchRepo.searchWorkspaceTasks(this.db, currentTaskId, ownerId, query, opts); } async readWorkspaceTaskAround(currentTaskId: number, ownerId: string, commentId: number, before: number, after: number) { return taskSearchRepo.readWorkspaceTaskAround(this.db, currentTaskId, ownerId, commentId, before, after); } async listLocalTaskComments(taskId: number): Promise { return localTasksRepo.listLocalTaskComments(this.db, taskId); } async getUninjectedComments(taskId: number, sinceId: number = 0): Promise { return localTasksRepo.getUninjectedComments(this.db, taskId, sinceId); } markCommentsInjected(commentIds: number[]): void { return localTasksRepo.markCommentsInjected(this.db, commentIds); } async getLatestResultComment(taskId: number): Promise<{ body: string; kind: string; createdAt: string } | null> { return localTasksRepo.getLatestResultComment(this.db, taskId); } getJobStatusSync(id: string): JobStatus | null { return jobsRepo.getJobStatusSync(this.db, id); } isWorkerBusy(workerId: string): boolean { return jobsRepo.isWorkerBusy(this.db, workerId); } listRunningJobOwnersByWorker(): Map> { return jobsRepo.listRunningJobOwnersByWorker(this.db); } async updateJob(id: string, updates: Partial>): Promise { return jobsRepo.updateJob(this.db, id, updates); } touchJobUpdatedAt(id: string): void { return jobsRepo.touchJobUpdatedAt(this.db, id); } resumeMcpWaitingJobs(ownerId: string, _serverId: string): number { return jobsRepo.resumeMcpWaitingJobs(this.db, ownerId, _serverId); } resumeToolRequestJob(jobId: string): number { return jobsRepo.resumeToolRequestJob(this.db, jobId); } resumePackageRequestJob(jobId: string): number { return jobsRepo.resumePackageRequestJob(this.db, jobId); } async lockIssue(repo: string, issueNumber: number, jobId: string): Promise { return jobsRepo.lockIssue(this.db, repo, issueNumber, jobId); } async unlockIssue(repo: string, issueNumber: number): Promise { return jobsRepo.unlockIssue(this.db, repo, issueNumber); } async deleteJobsForIssue(repo: string, issueNumber: number): Promise { return jobsRepo.deleteJobsForIssue(this.db, repo, issueNumber); } async addAuditLog(jobId: string | null, action: string, actor: string, detail: object): Promise { return auditRepo.addAuditLog(this.db, jobId, action, actor, detail); } oidcUpsert(model: string, id: string, payload: object, opts: { expiresAt?: number | null; grantId?: string | null; userCode?: string | null; uid?: string | null } = {}): void { return a2aRepo.oidcUpsert(this.db, model, id, payload, opts); } oidcFind(model: string, id: string): { payload: Record; consumedAt: number | null } | undefined { return a2aRepo.oidcFind(this.db, model, id); } oidcFindByUid(uid: string): { payload: Record } | undefined { return a2aRepo.oidcFindByUid(this.db, uid); } oidcFindByUserCode(userCode: string): { payload: Record } | undefined { return a2aRepo.oidcFindByUserCode(this.db, userCode); } oidcConsume(model: string, id: string, consumedAt: number): void { return a2aRepo.oidcConsume(this.db, model, id, consumedAt); } oidcDestroy(model: string, id: string): void { return a2aRepo.oidcDestroy(this.db, model, id); } oidcRevokeByGrantId(grantId: string): void { return a2aRepo.oidcRevokeByGrantId(this.db, grantId); } createA2aClient(row: A2aClientRow): void { return a2aRepo.createA2aClient(this.db, row); } getA2aClient(clientId: string): A2aClientRow | undefined { return a2aRepo.getA2aClient(this.db, clientId); } listA2aClients(): A2aClientRow[] { return a2aRepo.listA2aClients(this.db); } setA2aClientStatus(clientId: string, status: 'active' | 'disabled'): void { return a2aRepo.setA2aClientStatus(this.db, clientId, status); } createA2aDelegation(row: A2aDelegationRow): void { return a2aRepo.createA2aDelegation(this.db, row); } private mapDelegation(r: any): A2aDelegationRow { return a2aRepo.mapDelegation(r); } getA2aDelegationByGrantId(grantId: string): A2aDelegationRow | undefined { return a2aRepo.getA2aDelegationByGrantId(this.db, grantId); } listA2aDelegationsForUser(userId: string): A2aDelegationRow[] { return a2aRepo.listA2aDelegationsForUser(this.db, userId); } revokeA2aDelegation(id: string, revokedAtIso: string): void { return a2aRepo.revokeA2aDelegation(this.db, id, revokedAtIso); } getA2aDelegationById(id: string): A2aDelegationRow | undefined { return a2aRepo.getA2aDelegationById(this.db, id); } listLiveA2aDelegationsForClient(clientId: string, nowIso: string): A2aDelegationRow[] { return a2aRepo.listLiveA2aDelegationsForClient(this.db, clientId, nowIso); } listNonTerminalA2aTasksByGrant(grantId: string, limit = 500): Array<{ id: string; jobId: string | null; payload: Record }> { return a2aRepo.listNonTerminalA2aTasksByGrant(this.db, grantId, limit); } countNonTerminalA2aTasksByGrant(grantId: string): number { return a2aRepo.countNonTerminalA2aTasksByGrant(this.db, grantId); } cancelA2aLinkedJob(jobId: string): boolean { return a2aRepo.cancelA2aLinkedJob(this.db, jobId); } listA2aDelegationsForUserWithClient(userId: string): Array { return a2aRepo.listA2aDelegationsForUserWithClient(this.db, userId); } listA2aDelegationsAllWithClient(): Array< A2aDelegationRow & { userId: string; clientName: string | null; clientStatus: string | null } > { return a2aRepo.listA2aDelegationsAllWithClient(this.db); } getSpaceA2aSkills(spaceId: string): string[] { return spacesRepo.getSpaceA2aSkills(this.db, spaceId); } setSpaceA2aSkills(spaceId: string, skills: string[]): void { return spacesRepo.setSpaceA2aSkills(this.db, spaceId, skills); } saveA2aTask(row: { id: string; contextId: string | null; jobId: string | null; localTaskId: number | null; payload: object; delegationId?: string; grantId?: string; actingUserId?: string; }): void { return a2aRepo.saveA2aTask(this.db, row); } loadA2aTask(id: string): { payload: Record } | undefined { return a2aRepo.loadA2aTask(this.db, id); } getA2aTaskByJobId(jobId: string): { id: string; payload: Record } | undefined { return a2aRepo.getA2aTaskByJobId(this.db, jobId); } listNonTerminalA2aTasks(limit = 500): Array<{ id: string; jobId: string | null; payload: Record }> { return a2aRepo.listNonTerminalA2aTasks(this.db, limit); } async upsertWorkerNode(params: UpsertWorkerNodeParams): Promise { return workerNodesRepo.upsertWorkerNode(this.db, params); } async updateWorkerNodeHealth(workerId: string, updates: { healthy: boolean; lastError?: string | null; inflightJobs?: number; availableModels?: string[]; enabled?: boolean }): Promise { return workerNodesRepo.updateWorkerNodeHealth(this.db, workerId, updates); } async getWorkerNode(workerId: string): Promise { return workerNodesRepo.getWorkerNode(this.db, workerId); } async claimNextJob(workerId: string): Promise { return jobsRepo.claimNextJob(this.db, workerId); } async claimNextRetryJob(workerId: string): Promise { return jobsRepo.claimNextRetryJob(this.db, workerId); } async peekNextClaimable(workerId: string): Promise { return jobsRepo.peekNextClaimable(this.db, workerId); } async getJobsByStatus(status: JobStatus): Promise { return jobsRepo.getJobsByStatus(this.db, status); } async getLatestJobForIssue(repo: string, issueNumber: number): Promise { return jobsRepo.getLatestJobForIssue(this.db, repo, issueNumber); } async updateJobContext(jobId: string, payload: { promptTokens: number; limitTokens: number }): Promise { return jobsRepo.updateJobContext(this.db, jobId, payload); } async recoverOrphanedJobs(): Promise { return jobsRepo.recoverOrphanedJobs(this.db); } recoverStuckRunningJobs(staleMinutes: number): number { return jobsRepo.recoverStuckRunningJobs(this.db, staleMinutes); } requeueRunningJobs(): number { return jobsRepo.requeueRunningJobs(this.db); } getDistinctRepos(): string[] { return jobsRepo.getDistinctRepos(this.db); } getJobsByRepo(repoName: string): Job[] { return jobsRepo.getJobsByRepo(this.db, repoName); } getLatestJobsPerIssue(repoName: string): Job[] { return jobsRepo.getLatestJobsPerIssue(this.db, repoName); } async updateJobsVisibilityForTask(taskId: number, updates: { visibility: 'private' | 'org' | 'public'; visibilityScopeOrgId: string | null }): Promise { return jobsRepo.updateJobsVisibilityForTask(this.db, taskId, updates); } async getSubJobs(parentJobId: string): Promise { return jobsRepo.getSubJobs(this.db, parentJobId); } async requeueParentJobIfAllSubtasksDone(parentJobId: string): Promise { return jobsRepo.requeueParentJobIfAllSubtasksDone(this.db, parentJobId); } async deleteLocalTask(taskId: number, worktreeDir?: string): Promise { return localTasksRepo.deleteLocalTask(this.db, taskId, worktreeDir); } requestJobCancel(jobId: string): boolean { return jobsRepo.requestJobCancel(this.db, jobId); } private mapScheduledTask(row: any): ScheduledTask { return scheduledTasksRepo.mapScheduledTask(row); } async createScheduledTask(params: CreateScheduledTaskParams): Promise { return scheduledTasksRepo.createScheduledTask(this.db, params); } private getScheduledTaskSync(id: number): ScheduledTask | null { return scheduledTasksRepo.getScheduledTaskSync(this.db, id); } async getScheduledTask(id: number, opts?: { viewer?: Express.User }): Promise { return scheduledTasksRepo.getScheduledTask(this.db, id, opts); } async listScheduledTasks(filter?: { viewer?: Express.User; spaceId?: string | null }): Promise { return scheduledTasksRepo.listScheduledTasks(this.db, filter); } async getScheduledTasksDue(): Promise { return scheduledTasksRepo.getScheduledTasksDue(this.db); } async updateScheduledTask(id: number, params: UpdateScheduledTaskParams): Promise { return scheduledTasksRepo.updateScheduledTask(this.db, id, params); } async deleteScheduledTask(id: number): Promise { return scheduledTasksRepo.deleteScheduledTask(this.db, id); } createUser(params: CreateUserParams): User { return usersRepo.createUser(this.db, params); } ensureLocalUser(): void { return usersRepo.ensureLocalUser(this.db); } setLocalPassword(userId: string, plainPassword: string): void { return usersRepo.setLocalPassword(this.db, userId, plainPassword); } verifyLocalPassword(userId: string, plainPassword: string): boolean { return usersRepo.verifyLocalPassword(this.db, userId, plainPassword); } hasLocalCredential(userId: string): boolean { return usersRepo.hasLocalCredential(this.db, userId); } createLocalUser(params: CreateLocalUserParams): User { return usersRepo.createLocalUser(this.db, params); } upsertLocalSystemAdmin(params: { email: string; password: string; name?: string }): User { return usersRepo.upsertLocalSystemAdmin(this.db, params); } private rowToLocalOrg(r: { id: string; name: string; created_by: string | null; created_at: string }): LocalOrg { return usersRepo.rowToLocalOrg(r); } createLocalOrg(name: string, createdBy: string | null): LocalOrg { return usersRepo.createLocalOrg(this.db, name, createdBy); } getLocalOrg(id: string): LocalOrg | null { return usersRepo.getLocalOrg(this.db, id); } listLocalOrgs(): LocalOrg[] { return usersRepo.listLocalOrgs(this.db); } renameLocalOrg(id: string, name: string): void { return usersRepo.renameLocalOrg(this.db, id, name); } deleteLocalOrg(id: string): void { return usersRepo.deleteLocalOrg(this.db, id); } addOrgMember(orgId: string, userId: string, role: string = 'member'): void { return usersRepo.addOrgMember(this.db, orgId, userId, role); } removeOrgMember(orgId: string, userId: string): void { return usersRepo.removeOrgMember(this.db, orgId, userId); } listOrgMembers(orgId: string): LocalOrgMember[] { return usersRepo.listOrgMembers(this.db, orgId); } listUserLocalOrgs(userId: string): Array<{ orgId: string; name: string }> { return usersRepo.listUserLocalOrgs(this.db, userId); } getUserById(id: string): User | null { return usersRepo.getUserById(this.db, id); } getUserByEmail(email: string): User | null { return usersRepo.getUserByEmail(this.db, email); } findOrCreateUserByOAuth(params: FindOrCreateByOAuthParams): User { return usersRepo.findOrCreateUserByOAuth(this.db, params); } listUsers(): User[] { return usersRepo.listUsers(this.db); } listActiveUsersInOrgs(orgIds: string[]): User[] { return usersRepo.listActiveUsersInOrgs(this.db, orgIds); } updateUser(id: string, updates: { status?: 'active' | 'pending' | 'disabled'; role?: 'admin' | 'user'; email?: string; name?: string; avatarUrl?: string | null; defaultVisibility?: 'private' | 'org' | 'public'; defaultVisibilityOrgId?: string | null; }): void { return usersRepo.updateUser(this.db, id, updates); } deleteUser(id: string): void { return usersRepo.deleteUser(this.db, id); } deleteSessionsByUserId(userId: string): void { return usersRepo.deleteSessionsByUserId(this.db, userId); } replaceUserGiteaOrgs(userId: string, orgs: GiteaOrgInput[]): void { return usersRepo.replaceUserGiteaOrgs(this.db, userId, orgs); } listUserGiteaOrgs(userId: string): GiteaOrg[] { return usersRepo.listUserGiteaOrgs(this.db, userId); } recordPieceEdit(userId: string, pieceName: string, snapshotId: string): void { return reflectionRepo.recordPieceEdit(this.db, userId, pieceName, snapshotId); } countRecentPieceEdits(userId: string, pieceName: string, sinceMs: number): number { return reflectionRepo.countRecentPieceEdits(this.db, userId, pieceName, sinceMs); } recordReflectionRun(metric: { reflection_job_id: string; original_job_id: string | null; user_id: string; piece_name: string | null; outcome: 'applied' | 'partial' | 'abstained' | 'rejected' | 'failed'; memory_changes: number; piece_edited: 0 | 1; tokens_in: number; tokens_out: number; duration_ms: number; }, pieceEdit?: { pieceName: string; snapshotId: string }): void { return reflectionRepo.recordReflectionRun(this.db, metric, pieceEdit); } recordReflectionMetric(row: { reflection_job_id: string; original_job_id: string | null; user_id: string; piece_name: string | null; outcome: 'applied' | 'partial' | 'abstained' | 'rejected' | 'failed'; memory_changes: number; piece_edited: 0 | 1; tokens_in: number; tokens_out: number; duration_ms: number; }): void { return reflectionRepo.recordReflectionMetric(this.db, row); } aggregateReflectionMetrics(userId: string, sinceMs: number): { applied: number; partial: number; abstained: number; rejected: number; failed: number; tokensIn: number; tokensOut: number; pieceEdits: number; totalRuns: number; } { return reflectionRepo.aggregateReflectionMetrics(this.db, userId, sinceMs); } private tzModifier(tzOffsetMin: number): string { return calendarRepo.tzModifier(tzOffsetMin); } async createCalendarEvent(params: { spaceId: string; ownerId?: string | null; date: string; endDate?: string | null; time?: string | null; endTime?: string | null; title: string; description?: string | null; createdBy?: 'user' | 'agent'; sourceTaskId?: number | null; }): Promise { return calendarRepo.createCalendarEvent(this.db, params); } recordToolRequest(params: { id?: string; taskId?: string | null; jobId?: string | null; spaceId?: string | null; pieceName: string; movementName: string; toolName: string; reason?: string | null; category?: ToolRequestCategory; status?: ToolRequestStatus; }): string { return toolRequestsRepo.recordToolRequest(this.db, params); } listToolRequestsByTask(taskId: string): ToolRequest[] { return toolRequestsRepo.listToolRequestsByTask(this.db, taskId); } aggregateToolRequestsByPiece(pieceName: string): ToolRequestAggregate[] { return toolRequestsRepo.aggregateToolRequestsByPiece(this.db, pieceName); } getToolRequest(id: string): ToolRequest | null { return toolRequestsRepo.getToolRequest(this.db, id); } decideToolRequest(id: string, decision: { status: Exclude; grantScope?: 'task' | 'piece' | null; decidedBy?: string | null }): boolean { return toolRequestsRepo.decideToolRequest(this.db, id, decision); } getGrantedTools(taskId: string): string[] { return toolRequestsRepo.getGrantedTools(this.db, taskId); } addGrantedTool(taskId: string, toolName: string): string[] { return toolRequestsRepo.addGrantedTool(this.db, taskId, toolName); } recordPackageRequest(params: { id?: string; taskId?: string | null; jobId?: string | null; spaceId?: string | null; pieceName?: string | null; movementName?: string | null; spec: string; normalizedName: string; reason?: string | null; status?: PackageRequestStatus; }): string { return toolRequestsRepo.recordPackageRequest(this.db, params); } listPackageRequestsByTask(taskId: string): PackageRequest[] { return toolRequestsRepo.listPackageRequestsByTask(this.db, taskId); } listDeclinedPackageNamesByJob(jobId: string): string[] { return toolRequestsRepo.listDeclinedPackageNamesByJob(this.db, jobId); } getPackageRequest(id: string): PackageRequest | null { return toolRequestsRepo.getPackageRequest(this.db, id); } decidePackageRequest(id: string, decision: { status: Exclude; decidedBy?: string | null }): boolean { return toolRequestsRepo.decidePackageRequest(this.db, id, decision); } revertPackageRequestToPending(id: string): boolean { return toolRequestsRepo.revertPackageRequestToPending(this.db, id); } async listCalendarEvents(spaceId: string, range?: { from?: string; to?: string }): Promise { return calendarRepo.listCalendarEvents(this.db, spaceId, range); } async getCalendarEvent(eventId: number): Promise { return calendarRepo.getCalendarEvent(this.db, eventId); } async updateCalendarEvent(eventId: number, fields: { date?: string; endDate?: string | null; time?: string | null; endTime?: string | null; title?: string; description?: string | null }): Promise { return calendarRepo.updateCalendarEvent(this.db, eventId, fields); } async deleteCalendarEvent(eventId: number): Promise { return calendarRepo.deleteCalendarEvent(this.db, eventId); } private calendarTaskScope(spaceId: string, personalOwnerId?: string | null, alias = ''): { clause: string; params: unknown[] } { return calendarRepo.calendarTaskScope(spaceId, personalOwnerId, alias); } async getSpaceCalendarMonth(spaceId: string, opts: { monthStart: string; monthEnd: string; tzOffsetMin: number; personalOwnerId?: string | null }): Promise<{ days: Record; events: CalendarEvent[] }> { return calendarRepo.getSpaceCalendarMonth(this.db, spaceId, opts); } async getSpaceCalendarDay(spaceId: string, date: string, tzOffsetMin: number, personalOwnerId?: string | null): Promise<{ tasks: CalendarDayTask[]; events: CalendarEvent[] }> { return calendarRepo.getSpaceCalendarDay(this.db, spaceId, date, tzOffsetMin, personalOwnerId); } async getCrossSpaceCalendarMonth(viewer: Express.User, opts: { monthStart: string; monthEnd: string; tzOffsetMin: number }): Promise { return calendarRepo.getCrossSpaceCalendarMonth(this.db, viewer, opts); } createGatewayVirtualKey(params: { id?: string; keyHash: string; keyPrefix: string; team: string; allowedModels?: string[] | null; source?: GatewayVirtualKeySource; createdBy?: string | null; createdAt?: string; /** Phase 2b: optional monthly tokens budget. null/undefined = unlimited. */ tokensBudget?: number | null; /** Phase 2b: optional requests-per-minute cap. null/undefined = unlimited. */ rateLimitRpm?: number | null; }): GatewayVirtualKey { return gatewayRepo.createGatewayVirtualKey(this.db, params); } updateGatewayVirtualKey(id: string, patch: { /** * Phase 3a follow-up: team is now patchable so the config-migration * importer can propagate a YAML-side team rename to the DB. Admin * PATCH never sends this field (the team is intentionally immutable * via the public API to avoid an admin accidentally rewriting the * owner of a key); the only caller is importConfigKeysToDb. */ team?: string; tokensBudget?: number | null; rateLimitRpm?: number | null; allowedModels?: string[] | null; }): GatewayVirtualKey { return gatewayRepo.updateGatewayVirtualKey(this.db, id, patch); } findGatewayVirtualKeyByHash(keyHash: string): GatewayVirtualKey | null { return gatewayRepo.findGatewayVirtualKeyByHash(this.db, keyHash); } findGatewayVirtualKeyById(id: string): GatewayVirtualKey | null { return gatewayRepo.findGatewayVirtualKeyById(this.db, id); } listGatewayVirtualKeys(opts?: { team?: string; activeOnly?: boolean }): GatewayVirtualKey[] { return gatewayRepo.listGatewayVirtualKeys(this.db, opts); } revokeGatewayVirtualKey(id: string, revokedBy: string, at?: string): boolean { return gatewayRepo.revokeGatewayVirtualKey(this.db, id, revokedBy, at); } deleteGatewayVirtualKey(id: string): boolean { return gatewayRepo.deleteGatewayVirtualKey(this.db, id); } touchGatewayVirtualKeyLastUsed(id: string, at?: string): void { return gatewayRepo.touchGatewayVirtualKeyLastUsed(this.db, id, at); } getGatewayKeyUsage(keyId: string, periodStart: string): GatewayKeyUsage | null { return gatewayRepo.getGatewayKeyUsage(this.db, keyId, periodStart); } incrementGatewayKeyUsage(params: { keyId: string; period: string; tokensIn?: number; tokensOut?: number; requests?: number; at?: string; }): void { return gatewayRepo.incrementGatewayKeyUsage(this.db, params); } listGatewayKeyUsagesByKey(keyId: string, opts?: { limit?: number }): GatewayKeyUsage[] { return gatewayRepo.listGatewayKeyUsagesByKey(this.db, keyId, opts); } incrementLlmUsage(params: LlmUsageIncrement): void { return llmUsageRepo.incrementLlmUsage(this.db, params); } queryLlmUsageDaily(opts: { from: string; to: string; userId?: string }): LlmUsageDailyAgg[] { return llmUsageRepo.queryLlmUsageDaily(this.db, opts); } incrementLlmUsageHourly(params: LlmUsageHourlyIncrement): void { return llmUsageRepo.incrementLlmUsageHourly(this.db, params); } queryLlmUsageHourly(opts: { fromHour: string; toHour: string; userId?: string; }): LlmUsageHourlyRow[] { return llmUsageRepo.queryLlmUsageHourly(this.db, opts); } getUsageOrgMap(): Map { return llmUsageRepo.getUsageOrgMap(this.db); } /** Return the underlying Database instance (needed by migrate.ts and session store) */ getDb(): Database.Database { return this.db; } upsertPushSubscription(input: UpsertPushSubscriptionInput): { id: string } { return notificationsRepo.upsertPushSubscription(this.db, input); } listPushSubscriptionsForUser(userId: string): PushSubscriptionRecord[] { return notificationsRepo.listPushSubscriptionsForUser(this.db, userId); } getPushSubscriptionById(id: string): PushSubscriptionRecord | null { return notificationsRepo.getPushSubscriptionById(this.db, id); } deletePushSubscription(id: string): void { return notificationsRepo.deletePushSubscription(this.db, id); } markPushSubscriptionSuccess(id: string): void { return notificationsRepo.markPushSubscriptionSuccess(this.db, id); } markPushSubscriptionFailure(id: string): void { return notificationsRepo.markPushSubscriptionFailure(this.db, id); } getUserNotificationPrefs(userId: string): NotificationPrefs { return notificationsRepo.getUserNotificationPrefs(this.db, userId); } upsertUserNotificationPrefs(userId: string, update: NotificationPrefsUpdate): void { return notificationsRepo.upsertUserNotificationPrefs(this.db, userId, update); } markV1MigrationComplete(userId: string): boolean { return notificationsRepo.markV1MigrationComplete(this.db, userId); } close(): void { this.db.close(); } } export { BrowserSessionRepo } from './browser-session-repo.js'; export type { BrowserSessionProfile, CreateProfileInput, AuditInput } from './browser-session-repo.js';