Leon4gr45/builder
0
1import { randomUUID } from 'node:crypto';2import type { ServerTask } from './types';3 4interface TaskManagerOptions {5 maxConcurrentPerScope: number;6 keyTTLMs: number;7}8 9export class TaskManager {10 private tasks = new Map<string, ServerTask>();11 private apiKeys = new Map<string, { key: string; createdAt: number }>();12 private sweepInterval: ReturnType<typeof setInterval> | null = null;13 14 constructor(private readonly options: TaskManagerOptions) {15 this.sweepInterval = setInterval(() => this.sweepExpiredKeys(), 60_000);16 }17 18 createTask(projectId: string, sessionId: string, apiKey: string, workspaceId?: string): string {19 const scope = workspaceId ?? sessionId;20 const activeTasks = [...this.tasks.values()].filter(21 (t) => (t.workspaceId ?? t.sessionId) === scope && (t.status === 'running' || t.status === 'paused'),22 );23 24 if (activeTasks.length >= this.options.maxConcurrentPerScope) {25 throw new Error(`Concurrent task limit (${this.options.maxConcurrentPerScope}) reached`);26 }27 28 const taskId = randomUUID();29 const task: ServerTask = {30 taskId,31 projectId,32 sessionId,33 workspaceId,34 status: 'running',35 startedAt: Date.now(),36 orchestrator: null,37 buildDeferred: false,38 pendingBuildResolve: null,39 };40 41 this.tasks.set(taskId, task);42 this.apiKeys.set(taskId, { key: apiKey, createdAt: Date.now() });43 return taskId;44 }45 46 getTask(taskId: string): ServerTask | undefined {47 return this.tasks.get(taskId);48 }49 50 getApiKey(taskId: string): string | undefined {51 return this.apiKeys.get(taskId)?.key;52 }53 54 completeTask(taskId: string, status: 'completed' | 'failed' | 'cancelled'): void {55 const task = this.tasks.get(taskId);56 if (task) {57 task.status = status;58 task.orchestrator = null;59 }60 this.apiKeys.delete(taskId);61 }62 63 getTasksForSession(sessionId: string): ServerTask[] {64 return [...this.tasks.values()].filter((t) => t.sessionId === sessionId);65 }66 67 sweepExpiredKeys(): void {68 const now = Date.now();69 for (const [taskId, entry] of this.apiKeys) {70 if (now - entry.createdAt > this.options.keyTTLMs) {71 this.apiKeys.delete(taskId);72 }73 }74 for (const [taskId, task] of this.tasks) {75 if (task.status !== 'running' && task.status !== 'paused' && now - task.startedAt > this.options.keyTTLMs) {76 this.tasks.delete(taskId);77 }78 }79 }80 81 dispose(): void {82 if (this.sweepInterval) {83 clearInterval(this.sweepInterval);84 this.sweepInterval = null;85 }86 }87}88 