maestro/src/db/repository.ts
oss-sync 77ee3bc426
Some checks are pending
CI / build-and-test (push) Waiting to run
sync: update from private repo (ddadfd71)
2026-07-08 23:35:00 +00:00

1092 lines
40 KiB
TypeScript

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<SubtaskInfo[]> {
return jobsRepo.buildSubtaskInfos(this.db, subJobs, maxDepth);
}
async createJob(params: CreateJobParams): Promise<Job> {
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<Job | null> {
return jobsRepo.getJob(this.db, id, opts);
}
async createSpace(params: CreateSpaceParams): Promise<Space> {
return spacesRepo.createSpace(this.db, params);
}
async getSpace(id: string, opts?: { viewer?: Express.User }): Promise<Space | null> {
return spacesRepo.getSpace(this.db, id, opts);
}
async listSpaces(opts?: { viewer?: Express.User; includeArchived?: boolean }): Promise<Space[]> {
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<Space | null> {
return spacesRepo.updateSpace(this.db, id, patch);
}
async archiveSpace(id: string): Promise<void> {
return spacesRepo.archiveSpace(this.db, id);
}
async setSpaceWorkspaceDir(id: string, workspaceDir: string): Promise<void> {
return spacesRepo.setSpaceWorkspaceDir(this.db, id, workspaceDir);
}
async hardDeleteSpace(id: string): Promise<void> {
return spacesRepo.hardDeleteSpace(this.db, id);
}
async ensurePersonalSpace(ownerId: string, displayName?: string): Promise<Space> {
return spacesRepo.ensurePersonalSpace(this.db, ownerId, displayName);
}
async addSpaceMember(params: {
spaceId: string;
userId: string;
role: SpaceMemberRoleValue;
invitedBy?: string | null;
}): Promise<void> {
return spacesRepo.addSpaceMember(this.db, params);
}
async listSpaceMembers(spaceId: string): Promise<SpaceMember[]> {
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<void> {
return spacesRepo.updateSpaceMemberRole(this.db, spaceId, userId, role);
}
async removeSpaceMember(spaceId: string, userId: string): Promise<void> {
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<import('../spaces/folder-resolver.js').FolderContext> {
return localTasksRepo.resolveTaskFolderContext(this.db, taskId, userFolderRoot);
}
async createLocalTask(params: CreateLocalTaskParams): Promise<LocalTask> {
return localTasksRepo.createLocalTask(this.db, params);
}
async getLocalTask(taskId: number, opts?: { viewer?: Express.User }): Promise<LocalTask | null> {
return localTasksRepo.getLocalTask(this.db, taskId, opts);
}
async shareLocalTask(taskId: number): Promise<string> {
return localTasksRepo.shareLocalTask(this.db, taskId);
}
async unshareLocalTask(taskId: number): Promise<void> {
return localTasksRepo.unshareLocalTask(this.db, taskId);
}
async updateMissionBrief(taskId: number, patch: Partial<MissionBrief>): Promise<MissionBrief | null> {
return localTasksRepo.updateMissionBrief(this.db, taskId, patch);
}
updateMissionBriefSync(taskId: number, patch: Partial<MissionBrief>): 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<LocalTask | null> {
return localTasksRepo.getLocalTaskByShareToken(this.db, token);
}
async listLocalTasks(filter?: { ownerId?: string; viewer?: Express.User }): Promise<LocalTask[]> {
return localTasksRepo.listLocalTasks(this.db, filter);
}
async updateLocalTask(taskId: number, updates: Partial<Omit<LocalTask, 'id' | 'createdAt' | 'latestJob'>>): Promise<void> {
return localTasksRepo.updateLocalTask(this.db, taskId, updates);
}
async updateFeedback(taskId: number, feedback: {
rating: 'good' | 'bad';
tags: string[];
comment: string | null;
}): Promise<void> {
return localTasksRepo.updateFeedback(this.db, taskId, feedback);
}
async addLocalTaskComment(taskId: number, author: string, body: string, kind: string = 'comment', attachments: string[] = []): Promise<LocalTaskComment> {
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<Array<{ taskId: number; taskTitle: string; commentId: number; author: string; kind: string; createdAt: string; text: string }>> {
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<LocalTaskComment[]> {
return localTasksRepo.listLocalTaskComments(this.db, taskId);
}
async getUninjectedComments(taskId: number, sinceId: number = 0): Promise<LocalTaskComment[]> {
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<string, Array<{ user: string; kind: string }>> {
return jobsRepo.listRunningJobOwnersByWorker(this.db);
}
async updateJob(id: string, updates: Partial<Omit<Job, 'id' | 'createdAt'>>): Promise<void> {
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<boolean> {
return jobsRepo.lockIssue(this.db, repo, issueNumber, jobId);
}
async unlockIssue(repo: string, issueNumber: number): Promise<void> {
return jobsRepo.unlockIssue(this.db, repo, issueNumber);
}
async deleteJobsForIssue(repo: string, issueNumber: number): Promise<number> {
return jobsRepo.deleteJobsForIssue(this.db, repo, issueNumber);
}
async addAuditLog(jobId: string | null, action: string, actor: string, detail: object): Promise<void> {
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<string, unknown>; consumedAt: number | null } | undefined {
return a2aRepo.oidcFind(this.db, model, id);
}
oidcFindByUid(uid: string): { payload: Record<string, unknown> } | undefined {
return a2aRepo.oidcFindByUid(this.db, uid);
}
oidcFindByUserCode(userCode: string): { payload: Record<string, unknown> } | 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<string, unknown> }> {
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<A2aDelegationRow & { clientName: string | null; clientStatus: string | null }> {
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<string, unknown> } | undefined {
return a2aRepo.loadA2aTask(this.db, id);
}
getA2aTaskByJobId(jobId: string): { id: string; payload: Record<string, unknown> } | undefined {
return a2aRepo.getA2aTaskByJobId(this.db, jobId);
}
listNonTerminalA2aTasks(limit = 500): Array<{ id: string; jobId: string | null; payload: Record<string, unknown> }> {
return a2aRepo.listNonTerminalA2aTasks(this.db, limit);
}
async upsertWorkerNode(params: UpsertWorkerNodeParams): Promise<void> {
return workerNodesRepo.upsertWorkerNode(this.db, params);
}
async updateWorkerNodeHealth(workerId: string, updates: { healthy: boolean; lastError?: string | null; inflightJobs?: number; availableModels?: string[]; enabled?: boolean }): Promise<void> {
return workerNodesRepo.updateWorkerNodeHealth(this.db, workerId, updates);
}
async getWorkerNode(workerId: string): Promise<WorkerNode | null> {
return workerNodesRepo.getWorkerNode(this.db, workerId);
}
async claimNextJob(workerId: string): Promise<Job | null> {
return jobsRepo.claimNextJob(this.db, workerId);
}
async claimNextRetryJob(workerId: string): Promise<Job | null> {
return jobsRepo.claimNextRetryJob(this.db, workerId);
}
async peekNextClaimable(workerId: string): Promise<Job | null> {
return jobsRepo.peekNextClaimable(this.db, workerId);
}
async getJobsByStatus(status: JobStatus): Promise<Job[]> {
return jobsRepo.getJobsByStatus(this.db, status);
}
async getLatestJobForIssue(repo: string, issueNumber: number): Promise<Job | null> {
return jobsRepo.getLatestJobForIssue(this.db, repo, issueNumber);
}
async updateJobContext(jobId: string, payload: { promptTokens: number; limitTokens: number }): Promise<void> {
return jobsRepo.updateJobContext(this.db, jobId, payload);
}
async recoverOrphanedJobs(): Promise<number> {
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<number> {
return jobsRepo.updateJobsVisibilityForTask(this.db, taskId, updates);
}
async getSubJobs(parentJobId: string): Promise<Job[]> {
return jobsRepo.getSubJobs(this.db, parentJobId);
}
async requeueParentJobIfAllSubtasksDone(parentJobId: string): Promise<boolean> {
return jobsRepo.requeueParentJobIfAllSubtasksDone(this.db, parentJobId);
}
async deleteLocalTask(taskId: number, worktreeDir?: string): Promise<void> {
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<ScheduledTask> {
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<ScheduledTask | null> {
return scheduledTasksRepo.getScheduledTask(this.db, id, opts);
}
async listScheduledTasks(filter?: { viewer?: Express.User; spaceId?: string | null }): Promise<ScheduledTask[]> {
return scheduledTasksRepo.listScheduledTasks(this.db, filter);
}
async getScheduledTasksDue(): Promise<ScheduledTask[]> {
return scheduledTasksRepo.getScheduledTasksDue(this.db);
}
async updateScheduledTask(id: number, params: UpdateScheduledTaskParams): Promise<ScheduledTask | null> {
return scheduledTasksRepo.updateScheduledTask(this.db, id, params);
}
async deleteScheduledTask(id: number): Promise<boolean> {
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<CalendarEvent> {
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<ToolRequestStatus, 'pending'>; 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<PackageRequestStatus, 'pending'>; 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<CalendarEvent[]> {
return calendarRepo.listCalendarEvents(this.db, spaceId, range);
}
async getCalendarEvent(eventId: number): Promise<CalendarEvent | null> {
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<CalendarEvent | null> {
return calendarRepo.updateCalendarEvent(this.db, eventId, fields);
}
async deleteCalendarEvent(eventId: number): Promise<void> {
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<string, CalendarDayCounts>; 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<CrossSpaceCalendarMonth> {
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<string, string> {
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';